diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ce9d4483e59e9ba0c71c9aebbc2628bca7d1d10b..fb732fb07262d52a18b1a4c684c79fd471130019 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -123,7 +123,7 @@ test_api:
     - postgres:11
     - redis:5
   stage: test
-  image: $CI_REGISTRY/$IMAGE
+  image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
   cache:
     key: "$CI_PROJECT_ID__pip_cache"
     paths:
@@ -143,7 +143,7 @@ test_api:
     - pip3 install -r requirements/local.txt
     - pip3 install -r requirements/test.txt
   script:
-    - pytest --cov-report xml --cov=funkwhale_api --junitxml=report.xml tests/
+    - pytest --cov-report xml --cov-report term-missing --cov=funkwhale_api --junitxml=report.xml tests/
   tags:
     - docker
   artifacts:
@@ -209,8 +209,8 @@ build_backend:
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
   script:
     - cd api
-    - docker build -t $CI_REGISTRY/$IMAGE .
-    - docker push $CI_REGISTRY/$IMAGE
+    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME .
+    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
   tags:
     - docker-build
 
@@ -258,34 +258,34 @@ docker_release:
   tags:
     - docker-build
 
-docker_all_in_one_release:
-  stage: deploy
-  image: bash
-  variables:
-    ALL_IN_ONE_REF: master
-    ALL_IN_ONE_ARTIFACT_URL: https://github.com/thetarkus/docker-funkwhale/archive/$ALL_IN_ONE_REF.zip
-    BUILD_PATH: all_in_one
-  before_script:
-    - docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
-    - (if [ "$CI_COMMIT_REF_NAME" == "develop" ] || [ "$CI_COMMIT_REF_NAME" == "master" ]; then ./scripts/set-api-build-metadata.sh $(echo $CI_COMMIT_SHA | cut -c 1-8); fi);
-  script:
-    - if [[ ! -z "$CI_COMMIT_TAG" ]]; then (./docs/get-releases-json.py | scripts/is-docker-latest.py $CI_COMMIT_TAG -) && export DOCKER_LATEST_TAG="-t $ALL_IN_ONE_IMAGE_LATEST" || export DOCKER_LATEST_TAG=; fi
-    - wget $ALL_IN_ONE_ARTIFACT_URL -O all_in_one.zip
-    - unzip -o all_in_one.zip -d tmpdir
-    - mv tmpdir/docker-funkwhale-$ALL_IN_ONE_REF $BUILD_PATH && rmdir tmpdir
-    - cp -r api $BUILD_PATH/src/api
-    - cp -r front $BUILD_PATH/src/front
-    - cd $BUILD_PATH
-    - ./scripts/download-nginx-template.sh src/ $CI_COMMIT_REF_NAME
-    - docker build -t $ALL_IN_ONE_IMAGE $DOCKER_LATEST_TAG .
-    - docker push $ALL_IN_ONE_IMAGE
-    - if [[ ! -z "$DOCKER_LATEST_TAG" ]]; then docker push $ALL_IN_ONE_IMAGE_LATEST; fi
-  only:
-    - develop@funkwhale/funkwhale
-    - master@funkwhale/funkwhale
-    - tags@funkwhale/funkwhale
-  tags:
-    - docker-build
+#docker_all_in_one_release:
+#  stage: deploy
+#  image: bash
+#  variables:
+#    ALL_IN_ONE_REF: master
+#    ALL_IN_ONE_ARTIFACT_URL: https://github.com/thetarkus/docker-funkwhale/archive/$ALL_IN_ONE_REF.zip
+#    BUILD_PATH: all_in_one
+#  before_script:
+#    - docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
+#    - (if [ "$CI_COMMIT_REF_NAME" == "develop" ] || [ "$CI_COMMIT_REF_NAME" == "master" ]; then ./scripts/set-api-build-metadata.sh $(echo $CI_COMMIT_SHA | cut -c 1-8); fi);
+#  script:
+#    - if [[ ! -z "$CI_COMMIT_TAG" ]]; then (./docs/get-releases-json.py | scripts/is-docker-latest.py $CI_COMMIT_TAG -) && export DOCKER_LATEST_TAG="-t $ALL_IN_ONE_IMAGE_LATEST" || export DOCKER_LATEST_TAG=; fi
+#    - wget $ALL_IN_ONE_ARTIFACT_URL -O all_in_one.zip
+#    - unzip -o all_in_one.zip -d tmpdir
+#    - mv tmpdir/docker-funkwhale-$ALL_IN_ONE_REF $BUILD_PATH && rmdir tmpdir
+#    - cp -r api $BUILD_PATH/src/api
+#    - cp -r front $BUILD_PATH/src/front
+#    - cd $BUILD_PATH
+#    - ./scripts/download-nginx-template.sh src/ $CI_COMMIT_REF_NAME
+#    - docker build -t $ALL_IN_ONE_IMAGE $DOCKER_LATEST_TAG .
+#    - docker push $ALL_IN_ONE_IMAGE
+#    - if [[ ! -z "$DOCKER_LATEST_TAG" ]]; then docker push $ALL_IN_ONE_IMAGE_LATEST; fi
+#  only:
+#    - develop@funkwhale/funkwhale
+#    - master@funkwhale/funkwhale
+#    - tags@funkwhale/funkwhale
+#  tags:
+#    - docker-build
 
 build_api:
   # Simply publish a zip containing api/ directory
@@ -308,7 +308,7 @@ build_api:
 check_api_dependencies:
   interruptible: true
   stage: deps
-  image: $CI_REGISTRY/$IMAGE
+  image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
   cache:
     key: "$CI_PROJECT_ID__pip_cache"
     paths:
diff --git a/CHANGELOG b/CHANGELOG
index 0a96c495789398af81ebb3293f1b96c448d79cca..4c858c9dadb4d2874b5e67c69b03f171a37636e8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -882,8 +882,8 @@ Tags are used in various places to enhance user experience:
 - The custom radio builder now supports using tags
 - Subsonic apps that support genres - such as DSub or Ultrasonic - should display this information as well
 
-If you are a pod admin and want to extract tags from already uploaded content, you run `this snippet <https://dev.funkwhale.audio/funkwhale/funkwhale/snippets/43>`_
-and `this snippet <https://dev.funkwhale.audio/funkwhale/funkwhale/snippets/44>`_ in a ``python manage.py shell``.
+If you are a pod admin and want to extract tags from already uploaded content, you run `this snippet <https://dev.funkwhale.audio/funkwhale/funkwhale/snippets/43>`__
+and `this snippet <https://dev.funkwhale.audio/funkwhale/funkwhale/snippets/44>`__ in a ``python manage.py shell``.
 
 Content and account reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -899,8 +899,8 @@ Federation of the reports will be supported in a future release.
 
 For more information about this feature, please check out our documentation:
 
--  `User documentation <https://docs.funkwhale.audio/moderator/reports.html>`_
--  `Moderator documentation <https://docs.funkwhale.audio/users/reports.html>`_
+-  `User documentation <https://docs.funkwhale.audio/moderator/reports.html>`__
+-  `Moderator documentation <https://docs.funkwhale.audio/users/reports.html>`__
 
 Account deletion
 ^^^^^^^^^^^^^^^^
@@ -913,7 +913,7 @@ to other known servers on the federation.
 
 For more information about this feature, please check out our documentation:
 
--  `User documentation <https://docs.funkwhale.audio/users/account.html>`_
+-  `User documentation <https://docs.funkwhale.audio/users/account.html>`__
 
 Landing and about page redesign [Manual action suggested]
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -977,7 +977,7 @@ Both messages will appear for the first time 15 days after signup, in the notifi
 
 
 Replaced Daphne by Gunicorn/Uvicorn [manual action required, non-docker only]
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 To improve the performance, stability and reliability of Funkwhale's web processes,
 we now recommend using Gunicorn and Uvicorn instead of Daphne. This combination unlock new use cases such as:
@@ -1295,7 +1295,7 @@ special permissions, such as modifying instance settings or moderation (but this
 enabled in a future release).
 
 If you want to start building an app on top of Funkwhale's API, please check-out
-`https://docs.funkwhale.audio/api.html`_ and `https://docs.funkwhale.audio/developers/authentication.html`_.
+https://docs.funkwhale.audio/api.html and https://docs.funkwhale.audio/developers/authentication.html.
 
 Better error handling and display during import
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1313,7 +1313,7 @@ Storing all media files on the Funkwhale server itself may not be possible or de
 in all scenarios. You can now configure Funkwhale to store those files in a S3
 bucket instead.
 
-Check-out `https://docs.funkwhale.audio/admin/external-storages.html`_ if you want to use
+Check-out https://docs.funkwhale.audio/admin/external-storages.html if you want to use
 this feature.
 
 Prune library command
@@ -1324,7 +1324,7 @@ metadata even if no associated files exist.
 
 To help with that, we now offer a ``prune_library`` management command you can run
 to purge your database from obsolete entries. `Please refer to our documentation
-for usage instructions <https://docs.funkwhale.audio/admin/commands.html#pruning-library>`_.
+for usage instructions <https://docs.funkwhale.audio/admin/commands.html#pruning-library>`__.
 
 Check in-place files command
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1335,7 +1335,7 @@ files in the database, which results in unplayable tracks.
 
 To help with that, we now offer a ``check_inplace_files`` management command you can run
 to purge your database from obsolete files. `Please refer to our documentation
-for usage instructions <https://docs.funkwhale.audio/admin/commands.html#remove-obsolete-files-from-database>`_.
+for usage instructions <https://docs.funkwhale.audio/admin/commands.html#remove-obsolete-files-from-database>`__.
 
 
 Features:
@@ -2994,7 +2994,7 @@ Starting from this release, we now offer a dedicated interface directly
 in the front-end. You can view and edit all your instance settings from here,
 assuming you have the required permissions.
 
-This interface is available at ``/manage/settings` and via link in the sidebar.
+This interface is available at ``/manage/settings`` and via link in the sidebar.
 
 
 Storage of bitrate, size and length in database
@@ -3626,7 +3626,7 @@ Features:
 - Switched to django-channels and daphne for serving HTTP and websocket (#34)
 
 Upgrades notes
-**************
+^^^^^^^^^^^^^^
 
 This version contains breaking changes in the way funkwhale is deployed,
 please read the notes carefully.
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 432896d1f246a8380e807e258c8345a0c5734270..79f4893dff29bb2d6eb3efbf24d2a5ea286574f9 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -649,11 +649,11 @@ useful when testing components that depend on each other:
 
     def notify(email, message):
         """
-        A function that sends an email to the given recipient
+        A function that sends an e-mail to the given recipient
         with the given message
         """
 
-        # our email sending logic here
+        # our e-mail sending logic here
         # ...
 
     # funkwhale_api/myapp/users.py
@@ -675,9 +675,9 @@ useful when testing components that depend on each other:
     def test_downgrade_superuser_sends_email(factories, mocker):
         """
         Your downgrade logic is already tested, however, we want to ensure
-        an email is sent when user is downgraded, but we don't have any email
+        an e-mail is sent when user is downgraded, but we don't have any e-mail
         server available in our testing environment. Thus, we need to mock
-        the email sending process.
+        the e-mail sending process.
         """
         mocked_notify = mocker.patch('funkwhale_api.myapp.notifications.notify')
         user = factories['users.User'](is_superuser=True)
@@ -692,7 +692,7 @@ useful when testing components that depend on each other:
         user = factories['users.User'](is_superuser=False)
         users.downgrade_user(user)
 
-        # here, we ensure no email was sent
+        # here, we ensure no e-mail was sent
         mocked_notify.assert_not_called()
 
 Views: you can find some readable views tests in file: ``api/tests/users/test_views.py``
diff --git a/api/Dockerfile b/api/Dockerfile
index 1ba704e38739479f0f3c31ab58861a9b5d49c555..e339b78ffae145f94bb81a3c54b0c1e77990006d 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -17,6 +17,8 @@ RUN \
     openldap-dev \
     openssl-dev \
     cargo \
+    libxml2-dev \
+    libxslt-dev \
     && \
     \
     ln -s /usr/bin/python3 /usr/bin/python
@@ -57,6 +59,8 @@ RUN apk add --no-cache \
     jpeg-dev \
     ffmpeg             \
     libpq              \
+    libxml2 \
+    libxslt \
     && \
     \
     ln -s /usr/bin/python3 /usr/bin/python
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index d3389e6152da2d58a33d4d95001de0dfa8151fc9..dd040d14540cdaa1dd5850673ea7fa6e452acc9f 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -308,7 +308,7 @@ DEFAULT_FROM_EMAIL = env(
     "DEFAULT_FROM_EMAIL", default="Funkwhale <noreply@{}>".format(FUNKWHALE_HOSTNAME)
 )
 """
-Name and email address used to send system emails.
+Name and e-mail address used to send system e-mails.
 
 Default: ``Funkwhale <noreply@yourdomain>``
 
@@ -320,17 +320,17 @@ Default: ``Funkwhale <noreply@yourdomain>``
 """
 EMAIL_SUBJECT_PREFIX = env("EMAIL_SUBJECT_PREFIX", default="[Funkwhale] ")
 """
-Subject prefix for system emails.
+Subject prefix for system e-mails.
 """
 SERVER_EMAIL = env("SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
 
 
 EMAIL_CONFIG = env.email_url("EMAIL_CONFIG", default="consolemail://")
 """
-SMTP configuration for sending emails. Possible values:
+SMTP configuration for sending e-mails. Possible values:
 
-- ``EMAIL_CONFIG=consolemail://``: output emails to console (the default)
-- ``EMAIL_CONFIG=dummymail://``: disable email sending completely
+- ``EMAIL_CONFIG=consolemail://``: output e-mails to console (the default)
+- ``EMAIL_CONFIG=dummymail://``: disable e-mail sending completely
 
 On a production instance, you'll usually want to use an external SMTP server:
 
@@ -591,10 +591,11 @@ ACCOUNT_EMAIL_VERIFICATION_ENFORCE = env.bool(
     "ACCOUNT_EMAIL_VERIFICATION_ENFORCE", default=False
 )
 """
-Determine wether users need to verify their email address before using
-the service. Enabling this can be useful to reduce spam or bots accounts,
-however, you'll need to configure a mail server so that your users can receive
-the verification emails, using :attr:`EMAIL_CONFIG`.
+Determine wether users need to verify their e-mail address before using the service. Enabling this can be useful
+to reduce spam or bots accounts, however, you'll need to configure a mail server so that your users can receive the
+verification e-mails, using :attr:`EMAIL_CONFIG`.
+
+Note that regardless of the setting value, superusers created through the command line will never require verification.
 
 Note that regardless of the setting value, superusers created through the
 command line will never require verification.
@@ -626,6 +627,7 @@ OAUTH2_PROVIDER_APPLICATION_MODEL = "users.Application"
 OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "users.AccessToken"
 OAUTH2_PROVIDER_GRANT_MODEL = "users.Grant"
 OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "users.RefreshToken"
+OAUTH2_PROVIDER_ID_TOKEN_MODEL = "users.IdToken"
 
 SCOPED_TOKENS_MAX_AGE = 60 * 60 * 24 * 3
 
@@ -1255,7 +1257,7 @@ MODERATION_EMAIL_NOTIFICATIONS_ENABLED = env.bool(
     "MODERATION_EMAIL_NOTIFICATIONS_ENABLED", default=True
 )
 """
-Whether to enable email notifications to moderators and pods admins.
+Whether to enable e-mail notifications to moderators and pods admins.
 """
 FEDERATION_AUTHENTIFY_FETCHES = True
 FEDERATION_SYNCHRONOUS_FETCH = env.bool("FEDERATION_SYNCHRONOUS_FETCH", default=True)
diff --git a/api/config/settings/local.py b/api/config/settings/local.py
index 2fc121612d47dd5a4da1abd747c65f0f8c175a8f..3f97bc1ab28bea238560830dcc62a79230aca0b6 100644
--- a/api/config/settings/local.py
+++ b/api/config/settings/local.py
@@ -3,7 +3,7 @@
 Local settings
 
 - Run in Debug mode
-- Use console backend for emails
+- Use console backend for e-mails
 - Add Django Debug Toolbar
 - Add django-extensions as app
 """
diff --git a/api/config/settings/production.py b/api/config/settings/production.py
index 1ee9b8f7e6ef0270096a295bf7bc23d137a75c86..aba347af501d88cf7dce59711162ad62646177fa 100644
--- a/api/config/settings/production.py
+++ b/api/config/settings/production.py
@@ -4,7 +4,7 @@ Production Configurations
 
 - Use djangosecure
 - Use Amazon's S3 for storing static files and uploaded media
-- Use mailgun to send emails
+- Use mailgun to send e-mails
 - Use Redis on Heroku
 
 
diff --git a/api/funkwhale_api/cli/users.py b/api/funkwhale_api/cli/users.py
index 678b19f8108043114c022e979930de1f49f8eb20..92d57d3e6f5898087c32fdb2e000b3e0a0785057 100644
--- a/api/funkwhale_api/cli/users.py
+++ b/api/funkwhale_api/cli/users.py
@@ -37,7 +37,7 @@ def handler_create_user(
     utils.logger.debug("Validating user data…")
     serializer.is_valid(raise_exception=True)
 
-    # Override email validation, we assume accounts created from CLI have a valid email
+    # Override e-mail validation, we assume accounts created from CLI have a valid e-mail
     request = FakeRequest(session={"account_verified_email": email})
     utils.logger.debug("Creating user…")
     user = serializer.save(request=request)
diff --git a/api/funkwhale_api/contrib/scrobbler/scrobbler.py b/api/funkwhale_api/contrib/scrobbler/scrobbler.py
index f5a7ddd3f98abcbec7ff1fee5155fa053414356b..2d18ee778bfa7bcc6beba9513353ef8c45df29b2 100644
--- a/api/funkwhale_api/contrib/scrobbler/scrobbler.py
+++ b/api/funkwhale_api/contrib/scrobbler/scrobbler.py
@@ -90,7 +90,7 @@ def get_scrobble_payload(track, date, suffix="[0]"):
         "l{}".format(suffix): upload.duration if upload else 0,
         "b{}".format(suffix): (track.album.title if track.album else "") or "",
         "n{}".format(suffix): track.position or "",
-        "m{}".format(suffix): str(track.mbid) or "",
+        "m{}".format(suffix): str(track.mbid or ""),
         "o{}".format(suffix): "P",  # Source: P = chosen by user
     }
     if date:
@@ -115,7 +115,7 @@ def get_scrobble2_payload(track, date, suffix="[0]"):
     if track.position:
         data["trackNumber"] = track.position
     if track.mbid:
-        data["mbid"] = str(track.mbid)
+        data["mbid"] = str(track.mbid or "")
     if date:
         offset = upload.duration / 2 if upload.duration else 0
         data["timestamp"] = int(int(date.timestamp()) - offset)
diff --git a/api/funkwhale_api/contrib/sites/migrations/0004_alter_site_options.py b/api/funkwhale_api/contrib/sites/migrations/0004_alter_site_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..696f8b52aa947a2a18057e1f69a3e12850068350
--- /dev/null
+++ b/api/funkwhale_api/contrib/sites/migrations/0004_alter_site_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.4 on 2021-07-03 18:10
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sites', '0003_auto_20171214_2205'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='site',
+            options={'ordering': ['domain'], 'verbose_name': 'site', 'verbose_name_plural': 'sites'},
+        ),
+    ]
diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py
index 574560f0df0dc8957438b108acfa06983cab8fea..51aef3afdaba50cc44af0affb84ac1cd2902ff59 100644
--- a/api/funkwhale_api/federation/factories.py
+++ b/api/funkwhale_api/federation/factories.py
@@ -98,7 +98,7 @@ def get_cached_key_pair():
 
 
 @registry.register
-class ActorFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class ActorFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     public_key = None
     private_key = None
     preferred_username = factory.Faker("user_name")
@@ -154,7 +154,7 @@ class ActorFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
 
 
 @registry.register
-class FollowFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class FollowFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     target = factory.SubFactory(ActorFactory)
     actor = factory.SubFactory(ActorFactory)
 
@@ -234,7 +234,7 @@ class DeliveryFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
 
 
 @registry.register
-class LibraryFollowFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class LibraryFollowFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     target = factory.SubFactory(MusicLibraryFactory)
     actor = factory.SubFactory(ActorFactory)
 
diff --git a/api/funkwhale_api/federation/jsonld.py b/api/funkwhale_api/federation/jsonld.py
index f95b279f4dcef60222b8222cb378c1a868d716fc..d91690d1c93ffc46b9a3d216e704e2a087ce904b 100644
--- a/api/funkwhale_api/federation/jsonld.py
+++ b/api/funkwhale_api/federation/jsonld.py
@@ -46,6 +46,17 @@ def expand(doc, options=None, default_contexts=["AS", "FW", "SEC"]):
             # probably an already expanded document
             pass
 
+    # XXX This is a hotfix for a bug in pyld. The JSON-LD allows empty dicts or lists as part of the
+    # context, but this makes pyld failing to parse the context the right way. So we remove all
+    # empty items from the contexts
+    try:
+        for active_ctx in doc["@context"]:
+            if len(active_ctx) == 0:
+                doc["@context"].remove(active_ctx)
+    except KeyError:
+        # Nothing to do here if no context is available at all
+        pass
+
     result = pyld.jsonld.expand(doc, options=options)
     try:
         # jsonld.expand returns a list, which is useless for us
diff --git a/api/funkwhale_api/instance/dynamic_preferences_registry.py b/api/funkwhale_api/instance/dynamic_preferences_registry.py
index b61096e9c78a2ed1826c50c95fea5cc4c8b64b25..1b4dd10b4fa9e35ca42af53fd4e470eed7d90957 100644
--- a/api/funkwhale_api/instance/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/instance/dynamic_preferences_registry.py
@@ -73,7 +73,7 @@ class InstanceContactEmail(types.StringPreference):
     name = "contact_email"
     verbose_name = "Contact email"
     default = ""
-    help_text = "A contact email for visitors who need to contact an admin or moderator"
+    help_text = "A contact e-mail address for visitors who need to contact an admin or moderator"
     field_kwargs = {"required": False}
 
 
diff --git a/api/funkwhale_api/moderation/factories.py b/api/funkwhale_api/moderation/factories.py
index 35256285df023f4cf4d010f3286507d11f934f1c..74a1daa6529ef5775f51d187bab953f420659352 100644
--- a/api/funkwhale_api/moderation/factories.py
+++ b/api/funkwhale_api/moderation/factories.py
@@ -9,7 +9,7 @@ from . import serializers
 
 
 @registry.register
-class InstancePolicyFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class InstancePolicyFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     summary = factory.Faker("paragraph")
     actor = factory.SubFactory(federation_factories.ActorFactory)
     block_all = True
@@ -28,7 +28,7 @@ class InstancePolicyFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
 
 
 @registry.register
-class UserFilterFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class UserFilterFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     user = factory.SubFactory(users_factories.UserFactory)
     target_artist = None
 
@@ -42,7 +42,7 @@ class UserFilterFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
 
 
 @registry.register
-class NoteFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class NoteFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     author = factory.SubFactory(federation_factories.ActorFactory)
     target = None
     summary = factory.Faker("paragraph")
@@ -52,7 +52,7 @@ class NoteFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
 
 
 @registry.register
-class ReportFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class ReportFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     submitter = factory.SubFactory(federation_factories.ActorFactory)
     target = factory.SubFactory(music_factories.ArtistFactory)
     summary = factory.Faker("paragraph")
@@ -77,7 +77,7 @@ class ReportFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
 
 
 @registry.register
-class UserRequestFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
+class UserRequestFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
     submitter = factory.SubFactory(federation_factories.ActorFactory, local=True)
 
     class Meta:
diff --git a/api/funkwhale_api/moderation/serializers.py b/api/funkwhale_api/moderation/serializers.py
index 4b099a1b7590c69c1869fab851cf5b851c4c5408..b062744f4ac27cd96ec1e2eee6d1c8a6aeb85f73 100644
--- a/api/funkwhale_api/moderation/serializers.py
+++ b/api/funkwhale_api/moderation/serializers.py
@@ -304,7 +304,7 @@ class ReportSerializer(serializers.ModelSerializer):
 
         if not validated_data.get("submitter_email"):
             raise serializers.ValidationError(
-                "You need to provide an email address to submit this report"
+                "You need to provide an e-mail address to submit this report"
             )
 
         return validated_data
diff --git a/api/funkwhale_api/moderation/tasks.py b/api/funkwhale_api/moderation/tasks.py
index 6f908270fd3a483b1a48a69b188f743fd1743130..1e049540f1ff1256f14edc5ced14c3dc036d2afa 100644
--- a/api/funkwhale_api/moderation/tasks.py
+++ b/api/funkwhale_api/moderation/tasks.py
@@ -106,14 +106,16 @@ def send_new_report_email_to_moderators(report):
         "",
         "—",
         "",
-        "You are receiving this email because you are a moderator for {}.".format(
+        "You are receiving this e-mail because you are a moderator for {}.".format(
             settings.FUNKWHALE_HOSTNAME
         ),
     ]
 
     for moderator in moderators:
         if not moderator.email:
-            logger.warning("Moderator %s has no email configured", moderator.username)
+            logger.warning(
+                "Moderator %s has no e-mail address configured", moderator.username
+            )
             continue
         mail.send_mail(
             subject,
@@ -192,14 +194,16 @@ def notify_mods_signup_request_pending(obj):
         "",
         "—",
         "",
-        "You are receiving this email because you are a moderator for {}.".format(
+        "You are receiving this e-mail because you are a moderator for {}.".format(
             settings.FUNKWHALE_HOSTNAME
         ),
     ]
 
     for moderator in moderators:
         if not moderator.email:
-            logger.warning("Moderator %s has no email configured", moderator.username)
+            logger.warning(
+                "Moderator %s has no e-mail address configured", moderator.username
+            )
             continue
         mail.send_mail(
             subject,
@@ -213,7 +217,7 @@ def notify_submitter_signup_request_approved(user_request):
     submitter_repr = user_request.submitter.preferred_username
     submitter_email = user_request.submitter.user.email
     if not submitter_email:
-        logger.warning("User %s has no email configured", submitter_repr)
+        logger.warning("User %s has no e-mail address configured", submitter_repr)
         return
     subject = "Welcome to {}, {}!".format(settings.FUNKWHALE_HOSTNAME, submitter_repr)
     login_url = federation_utils.full_url("/login")
@@ -223,7 +227,7 @@ def notify_submitter_signup_request_approved(user_request):
         "Our moderation team has approved your account request and you can now start "
         "using the service. Please visit {} to get started.".format(login_url),
         "",
-        "Before your first login, you may need to verify your email address if you didn't already.",
+        "Before your first login, you may need to verify your e-mail address if you didn't already.",
     ]
 
     mail.send_mail(
@@ -238,7 +242,7 @@ def notify_submitter_signup_request_refused(user_request):
     submitter_repr = user_request.submitter.preferred_username
     submitter_email = user_request.submitter.user.email
     if not submitter_email:
-        logger.warning("User %s has no email configured", submitter_repr)
+        logger.warning("User %s has no e-mail address configured", submitter_repr)
         return
     subject = "Your account request at {} was refused".format(
         settings.FUNKWHALE_HOSTNAME
diff --git a/api/funkwhale_api/music/migrations/0054_alter_uploadversion_mimetype.py b/api/funkwhale_api/music/migrations/0054_alter_uploadversion_mimetype.py
new file mode 100644
index 0000000000000000000000000000000000000000..f91f3d67b3b4425182db59feea162f09479dbdf3
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0054_alter_uploadversion_mimetype.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2021-07-03 18:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0053_denormalize_audio_permissions'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='uploadversion',
+            name='mimetype',
+            field=models.CharField(choices=[('audio/mp3', 'mp3'), ('audio/mpeg3', 'mp3'), ('audio/x-mp3', 'mp3'), ('audio/mpeg', 'mp3'), ('video/ogg', 'ogg'), ('audio/ogg', 'ogg'), ('audio/opus', 'opus'), ('audio/x-m4a', 'aac'), ('audio/x-m4a', 'm4a'), ('audio/x-flac', 'flac'), ('audio/flac', 'flac'), ('audio/aiff', 'aif'), ('audio/x-aiff', 'aif'), ('audio/aiff', 'aiff'), ('audio/x-aiff', 'aiff')], max_length=50),
+        ),
+    ]
diff --git a/api/funkwhale_api/radios/radios.py b/api/funkwhale_api/radios/radios.py
index 5f37dc7e485868af1608c10fc9044f52535715b2..4f23fb28121727b181a0c80e425391a44063c668 100644
--- a/api/funkwhale_api/radios/radios.py
+++ b/api/funkwhale_api/radios/radios.py
@@ -5,11 +5,12 @@ from django.db import connection
 from django.db.models import Q
 from rest_framework import serializers
 
-from funkwhale_api.federation import models as federation_models
 from funkwhale_api.federation import fields as federation_fields
+from funkwhale_api.federation import models as federation_models
 from funkwhale_api.moderation import filters as moderation_filters
 from funkwhale_api.music.models import Artist, Library, Track, Upload
 from funkwhale_api.tags.models import Tag
+
 from . import filters, models
 from .registries import registry
 
@@ -320,3 +321,10 @@ class LibraryRadio(RelatedObjectRadio):
 
     def get_related_object_id_repr(self, obj):
         return obj.uuid
+
+
+@registry.register(name="recently-added")
+class RecentlyAdded(SessionRadio):
+    def get_queryset(self, **kwargs):
+        qs = super().get_queryset(**kwargs)
+        return qs.filter(artist__content_category="music").order_by("-creation_date")
diff --git a/api/funkwhale_api/subsonic/authentication.py b/api/funkwhale_api/subsonic/authentication.py
index dd6a60f42f34461b14f59845269083ed8c7abd07..6289248e95990fb926fec9fcb2a95ce22956c59a 100644
--- a/api/funkwhale_api/subsonic/authentication.py
+++ b/api/funkwhale_api/subsonic/authentication.py
@@ -28,7 +28,7 @@ def authenticate(username, password):
         raise exceptions.AuthenticationFailed("Wrong username or password.")
 
     if common_authentication.should_verify_email(user):
-        raise exceptions.AuthenticationFailed("You need to verify your email.")
+        raise exceptions.AuthenticationFailed("You need to verify your e-mail address.")
 
     return (user, None)
 
@@ -47,7 +47,7 @@ def authenticate_salt(username, salt, token):
         raise exceptions.AuthenticationFailed("Wrong username or password.")
 
     if common_authentication.should_verify_email(user):
-        raise exceptions.AuthenticationFailed("You need to verify your email.")
+        raise exceptions.AuthenticationFailed("You need to verify your e-mail address.")
 
     return (user, None)
 
diff --git a/api/funkwhale_api/templates/registration/password_reset_email.html b/api/funkwhale_api/templates/registration/password_reset_email.html
index 0b6b1384d89036f8a3eb3f8b708559e721c8cca4..08f25472ddcf4e1ed39ab5e2a515898d478b7aab 100644
--- a/api/funkwhale_api/templates/registration/password_reset_email.html
+++ b/api/funkwhale_api/templates/registration/password_reset_email.html
@@ -1,5 +1,5 @@
 {% load i18n %}{% autoescape off %}
-{% blocktrans with site_name=funkwhale_site_name %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
+{% blocktrans with site_name=funkwhale_site_name %}You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
 
 {% trans "Please go to the following page and choose a new password:" %}
 {{ funkwhale_url }}/auth/password/reset/confirm?uid={{ uid }}&token={{ token }}
diff --git a/api/funkwhale_api/users/migrations/0021_auto_20210703_1810.py b/api/funkwhale_api/users/migrations/0021_auto_20210703_1810.py
new file mode 100644
index 0000000000000000000000000000000000000000..247fc34bf96f69ab5db969e1f6b1a82c22c6910b
--- /dev/null
+++ b/api/funkwhale_api/users/migrations/0021_auto_20210703_1810.py
@@ -0,0 +1,67 @@
+# Generated by Django 3.2.4 on 2021-07-03 18:10
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0020_application_token'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='application',
+            name='algorithm',
+            field=models.CharField(blank=True, choices=[('', 'No OIDC support'), ('RS256', 'RSA with SHA-2 256'), ('HS256', 'HMAC with SHA-2 256')], default='', max_length=5),
+        ),
+        migrations.AddField(
+            model_name='grant',
+            name='claims',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='grant',
+            name='nonce',
+            field=models.CharField(blank=True, default='', max_length=255),
+        ),
+        migrations.AlterField(
+            model_name='application',
+            name='authorization_grant_type',
+            field=models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials'), ('openid-hybrid', 'OpenID connect hybrid')], max_length=32),
+        ),
+        migrations.AlterField(
+            model_name='grant',
+            name='redirect_uri',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='first_name',
+            field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
+        ),
+        migrations.CreateModel(
+            name='IdToken',
+            fields=[
+                ('id', models.BigAutoField(primary_key=True, serialize=False)),
+                ('jti', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='JWT Token ID')),
+                ('expires', models.DateTimeField()),
+                ('scope', models.TextField(blank=True)),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
+                ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='users_idtoken', to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.AddField(
+            model_name='accesstoken',
+            name='id_token',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_token', to=settings.OAUTH2_PROVIDER_ID_TOKEN_MODEL),
+        ),
+    ]
diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py
index 450404a638acec5c7d2900f1347de003cc69221d..a07b92efa7c4a83061b10026e784344c7f1285c8 100644
--- a/api/funkwhale_api/users/models.py
+++ b/api/funkwhale_api/users/models.py
@@ -393,6 +393,10 @@ class RefreshToken(oauth2_models.AbstractRefreshToken):
     pass
 
 
+class IdToken(oauth2_models.AbstractIDToken):
+    pass
+
+
 def get_actor_data(username, **kwargs):
     slugified_username = federation_utils.slugify_username(username)
     domain = kwargs.get("domain")
diff --git a/api/funkwhale_api/users/oauth/scopes.py b/api/funkwhale_api/users/oauth/scopes.py
index f5390eb0452bb594e466196a5c664a94deddcc07..29d5ddae00907cabf126f8dac7d2d2c80ac13a65 100644
--- a/api/funkwhale_api/users/oauth/scopes.py
+++ b/api/funkwhale_api/users/oauth/scopes.py
@@ -10,7 +10,7 @@ class Scope:
 
 BASE_SCOPES = [
     Scope(
-        "profile", "Access profile data (email, username, avatar, subsonic password…)"
+        "profile", "Access profile data (e-mail, username, avatar, subsonic password…)"
     ),
     Scope("libraries", "Access uploads, libraries, and audio metadata"),
     Scope("edits", "Browse and submit edits on audio metadata"),
diff --git a/api/funkwhale_api/users/rest_auth_urls.py b/api/funkwhale_api/users/rest_auth_urls.py
index 75ba608bb92fc1dd2b91d21661b2b6fd370c26f0..540ea82851856c4ff9f4f294bab1883ba6120a68 100644
--- a/api/funkwhale_api/users/rest_auth_urls.py
+++ b/api/funkwhale_api/users/rest_auth_urls.py
@@ -38,8 +38,8 @@ urlpatterns = [
         name="change_password",
     ),
     # This url is used by django-allauth and empty TemplateView is
-    # defined just to allow reverse() call inside app, for example when email
-    # with verification link is being sent, then it's required to render email
+    # defined just to allow reverse() call inside app, for example when e-mail
+    # with verification link is being sent, then it's required to render e-mail
     # content.
     # account_confirm_email - You should override this view to handle it in
     # your API client somehow and then, send post to /verify-email/ endpoint
diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py
index 72bc5833fb00a804147f35ca2db367918459db88..3173274590f269d27a62dc82c40ea6a8cb7e7010 100644
--- a/api/funkwhale_api/users/serializers.py
+++ b/api/funkwhale_api/users/serializers.py
@@ -294,7 +294,7 @@ class UserChangeEmailSerializer(serializers.Serializer):
             .exclude(user=self.context["user"])
             .exists()
         ):
-            raise serializers.ValidationError("This email address is already in use")
+            raise serializers.ValidationError("This e-mail address is already in use")
         return value
 
     def save(self, request):
diff --git a/api/funkwhale_api/users/views.py b/api/funkwhale_api/users/views.py
index f177bb1e409b1d7a44e8efa12cb437e2554054ea..b9891e594e9c38a326414e33c30535f047c64647 100644
--- a/api/funkwhale_api/users/views.py
+++ b/api/funkwhale_api/users/views.py
@@ -38,7 +38,7 @@ class RegisterView(registration_views.RegisterView):
     def perform_create(self, serializer):
         user = super().perform_create(serializer)
         if not user.is_active:
-            # manual approval, we need to send the confirmation email by hand
+            # manual approval, we need to send the confirmation e-mail by hand
             authentication.send_email_confirmation(self.request, user)
         return user
 
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index 4f39ea82705f4c4a773769ba3bd1895233576a4f..516ade2b31cbcdb4d8e3bf6e68299da4891e68f5 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -8,17 +8,17 @@ Pillow~=8.2.0
 
 django-allauth~=0.42.0
 
-psycopg2-binary~=2.8.0
+psycopg2~=2.9.1
 
 # Time zones support
 pytz==2021.1
 
 # Redis support
-django-redis~=4.12.0
+django-redis~=5.0.0
 redis~=3.5.0
-kombu~=5.0.0
+kombu~=5.1.0
 
-celery~=5.0.0
+celery~=5.1.2
 
 # Your custom requirements go here
 django-cors-headers~=3.7.0
@@ -37,8 +37,8 @@ pymemoize~=1.0.0
 django-dynamic-preferences~=1.10
 python-magic~=0.4.0
 channels~=2.4.0
-channels_redis~=3.2.0
-uvicorn[standard]~=0.13.4
+channels_redis~=3.3.0
+uvicorn[standard]~=0.14.0
 gunicorn~=20.1.0
 
 cryptography~=3.4.7
@@ -54,10 +54,10 @@ python-ldap~=3.3.0
 django-auth-ldap~=2.4.0
 
 pydub~=0.25.1
-pyld~=1.0.0
+pyld~=2.0.3
 aiohttp~=3.7.4
 
-django-oauth-toolkit~=1.3.0
+django-oauth-toolkit~=1.5.0
 django-storages~=1.11.1
 boto3~=1.17.59
 unicode-slugify~=0.1.0
@@ -72,3 +72,4 @@ watchdog~=2.1.2
 
 ## Pin third party dependency to avoid issue with latest version
 twisted==20.3.0
+asgiref==3.3.4
diff --git a/api/requirements/local.txt b/api/requirements/local.txt
index f321c81c6a0f4f4256a9e90649d89e6838b37e87..fa53b23cb8e999bab41c2a34235ab35ee0c065c1 100644
--- a/api/requirements/local.txt
+++ b/api/requirements/local.txt
@@ -1,8 +1,8 @@
 # Local development dependencies go here
 
 coverage~=5.5.0
-django_coverage_plugin~=1.8.0
-factory_boy~=2.12.0
+django_coverage_plugin~=2.0.0
+factory_boy~=3.2.0
 
 # django-debug-toolbar that works with Django 1.5+
 django-debug-toolbar~=3.2.1
diff --git a/api/requirements/test.txt b/api/requirements/test.txt
index 0f217da3d01079c85c82936aade9aa427761b098..0821198f06a362aa627f5353d64f75432eed980a 100644
--- a/api/requirements/test.txt
+++ b/api/requirements/test.txt
@@ -3,10 +3,10 @@
 flake8~=3.9.1
 pytest~=6.2.3
 pytest-cov~=2.12.0
-pytest-django~=4.3.0
+pytest-django~=4.4.0
 pytest-env~=0.6.0
 pytest-mock~=3.6.0
 pytest-randomly~=3.8.0
 pytest-sugar~=0.9.0
 requests-mock~=1.9.0
-faker~=8.2.0
+faker~=8.9.1
diff --git a/api/tests/moderation/test_serializers.py b/api/tests/moderation/test_serializers.py
index 9089dc371547a60cfd3b46e3ac04fdd8939c9cb6..2763fc8b1ff8ed30786f462ea79c083608469115 100644
--- a/api/tests/moderation/test_serializers.py
+++ b/api/tests/moderation/test_serializers.py
@@ -203,9 +203,9 @@ def test_report_serializer_repr(factories, to_api_date):
             {"type": "other", "submitter_email": "hello@example.test"},
             False,
         ),
-        # anonymous reports enabled for the category, but invalid email
+        # anonymous reports enabled for the category, but invalid e-mail
         (["other"], {}, {"type": "other", "submitter_email": "hello@"}, False),
-        # anonymous reports enabled for the category, no email
+        # anonymous reports enabled for the category, no e-mail
         (["other"], {}, {"type": "other"}, False),
         # anonymous reports enabled for the category, actor object is empty
         (["other"], {"submitter": None}, {"type": "other"}, False),
diff --git a/api/tests/moderation/test_tasks.py b/api/tests/moderation/test_tasks.py
index f51d8ff3bc665bd2a36c4a04820357be45fce163..54f0438dc33ffa3cd85685c875d9392deb7ef0b8 100644
--- a/api/tests/moderation/test_tasks.py
+++ b/api/tests/moderation/test_tasks.py
@@ -15,9 +15,9 @@ def test_report_created_signal_calls_send_new_report_mail(factories, mocker):
 def test_report_created_signal_sends_email_to_mods(factories, mailoutbox, settings):
     mod1 = factories["users.User"](permission_moderation=True)
     mod2 = factories["users.User"](permission_moderation=True)
-    # inactive, so no email
+    # inactive, so no e-mail
     factories["users.User"](permission_moderation=True, is_active=False)
-    # no moderation permission, so no email
+    # no moderation permission, so no e-mail
     factories["users.User"]()
 
     report = factories["moderation.Report"]()
diff --git a/api/tests/users/oauth/test_views.py b/api/tests/users/oauth/test_views.py
index 96f594ec2038fc38696e01dbd0eb96ff6fa6a448..95f5aec77550fee91c42eab09dbdb00351998f46 100644
--- a/api/tests/users/oauth/test_views.py
+++ b/api/tests/users/oauth/test_views.py
@@ -427,5 +427,5 @@ def test_token_auth(
     assert response.status_code == expected_status_code
 
     if expected_status_code != 200:
-        # confirmation email should have been sent again
+        # confirmation e-mail should have been sent again
         assert len(mailoutbox) == sent_emails + 1
diff --git a/changes/changelog.d/1314.doc b/changes/changelog.d/1314.doc
new file mode 100644
index 0000000000000000000000000000000000000000..2b7c4cb4f984c2a435e33e5fdff6d752d9a5638d
--- /dev/null
+++ b/changes/changelog.d/1314.doc
@@ -0,0 +1 @@
+Added server uninstallation documentation (\!1314)
diff --git a/changes/changelog.d/1345.doc b/changes/changelog.d/1345.doc
new file mode 100644
index 0000000000000000000000000000000000000000..a87644531da3f87615b9ca9486fe0b4c6e3c333a
--- /dev/null
+++ b/changes/changelog.d/1345.doc
@@ -0,0 +1 @@
+Fixed broken backup documentation (#1345)
diff --git a/changes/changelog.d/1390.feature b/changes/changelog.d/1390.feature
new file mode 100644
index 0000000000000000000000000000000000000000..22516579b419556f880cc47ecd413929ac296c58
--- /dev/null
+++ b/changes/changelog.d/1390.feature
@@ -0,0 +1 @@
+Implemented awesome recently added radio (part of #1390)
diff --git a/changes/changelog.d/1427.bugfix b/changes/changelog.d/1427.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..a017d9eb93cd48ff7bae906f3061e03cbba005be
--- /dev/null
+++ b/changes/changelog.d/1427.bugfix
@@ -0,0 +1 @@
+Only suggest typed tag once if it already exists
diff --git a/changes/changelog.d/1489.bugfix b/changes/changelog.d/1489.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..dd73b4205cf2814364cd1fc7c9177f3f57ee2fac
--- /dev/null
+++ b/changes/changelog.d/1489.bugfix
@@ -0,0 +1 @@
+Add worker-src to nginx header to prevent issues (#1489)
diff --git a/changes/changelog.d/1498.bug b/changes/changelog.d/1498.bug
new file mode 100644
index 0000000000000000000000000000000000000000..5290c9e2aefcba3747c846f85e62a6043fd016e0
--- /dev/null
+++ b/changes/changelog.d/1498.bug
@@ -0,0 +1 @@
+Fix the scrobbler plugin submitting literal "None" as MusicBrainz ID (#1498)
diff --git a/changes/changelog.d/1513.enhancement b/changes/changelog.d/1513.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..c8beb0ed1014851be41de4e2bfb64a93026e4da2
--- /dev/null
+++ b/changes/changelog.d/1513.enhancement
@@ -0,0 +1 @@
+Replace psycopg2-binary with psycopg2 (#1513)
diff --git a/changes/changelog.d/refactored.doc b/changes/changelog.d/refactored.doc
new file mode 100644
index 0000000000000000000000000000000000000000..813ed70a96efabf220693bd2aa1893bce521f9ed
--- /dev/null
+++ b/changes/changelog.d/refactored.doc
@@ -0,0 +1 @@
+Refactored installation documentation and other small documentation adjustments (\!1314)
diff --git a/deploy/apache.conf b/deploy/apache.conf
index 996e5488c35efe5770d5ba8022ea98a06f34282b..0c072059e85df8d396daaa067b0e18fe4d12a900 100644
--- a/deploy/apache.conf
+++ b/deploy/apache.conf
@@ -57,7 +57,7 @@ Define MEDIA_DIRECTORY_PATH ${FUNKWHALE_ROOT_PATH}/data/media
    </IfModule>
 
    # Turning ProxyRequests on and allowing proxying from all may allow
-   # spammers to use your proxy to send email.
+   # spammers to use your proxy to send e-mail.
    ProxyRequests Off
 
    <Proxy *>
diff --git a/deploy/env.prod.sample b/deploy/env.prod.sample
index f52f819e1892ba8688c60558fe9c48cd06214bbc..69b3e466e2fa24fdd31f848995a3dbef54bd37ce 100644
--- a/deploy/env.prod.sample
+++ b/deploy/env.prod.sample
@@ -5,7 +5,7 @@
 # following variables:
 # - DJANGO_SECRET_KEY
 # - FUNKWHALE_HOSTNAME
-# - EMAIL_CONFIG and DEFAULT_FROM_EMAIL if you plan to send emails)
+# - EMAIL_CONFIG and DEFAULT_FROM_EMAIL if you plan to send e-mails)
 # On non-docker setup **only**, you'll also have to tweak/uncomment those variables:
 # - DATABASE_URL
 # - CACHE_URL
@@ -43,21 +43,21 @@ FUNKWHALE_WEB_WORKERS=4
 FUNKWHALE_HOSTNAME=yourdomain.funkwhale
 FUNKWHALE_PROTOCOL=https
 
-# Configure email sending using this variale
-# By default, funkwhale will output emails sent to stdout
+# Configure e-mail sending using this variale
+# By default, funkwhale will output e-mails sent to stdout
 # here are a few examples for this setting
-# EMAIL_CONFIG=consolemail://         # output emails to console (the default)
-# EMAIL_CONFIG=dummymail://          # disable email sending completely
+# EMAIL_CONFIG=consolemail://         # output e-mails to console (the default)
+# EMAIL_CONFIG=dummymail://          # disable e-mail sending completely
 # On a production instance, you'll usually want to use an external SMTP server:
 # EMAIL_CONFIG=smtp://user@:password@youremail.host:25
 # EMAIL_CONFIG=smtp+ssl://user@:password@youremail.host:465
 # EMAIL_CONFIG=smtp+tls://user@:password@youremail.host:587
 
-# Make email verification mandatory before using the service
+# Make e-mail verification mandatory before using the service
 # Doesn't apply to admins.
 # ACCOUNT_EMAIL_VERIFICATION_ENFORCE=false
 
-# The email address to use to send system emails.
+# The e-mail address to use to send system e-mails.
 # DEFAULT_FROM_EMAIL=noreply@yourdomain
 
 # Depending on the reverse proxy used in front of your funkwhale instance,
diff --git a/deploy/nginx.template b/deploy/nginx.template
index aaa3cec7dc1af8dce7270ba2aeb8c3e900243732..575030bbac99af2a52e51f3446b9d29390432ecb 100644
--- a/deploy/nginx.template
+++ b/deploy/nginx.template
@@ -44,7 +44,7 @@ server {
     # If you are using S3 to host your files, remember to add your S3 URL to the
     # media-src and img-src headers (e.g. img-src 'self' https://<your-S3-URL> data:)
 
-    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:";
+    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'";
     add_header Referrer-Policy "strict-origin-when-cross-origin";
 
     root ${FUNKWHALE_FRONTEND_PATH};
@@ -84,7 +84,7 @@ server {
     }
 
     location /front/ {
-        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:";
+        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'";
         add_header Referrer-Policy "strict-origin-when-cross-origin";
         add_header Service-Worker-Allowed "/";
         add_header X-Frame-Options "SAMEORIGIN";
@@ -94,7 +94,7 @@ server {
         add_header Cache-Control "public, must-revalidate, proxy-revalidate";
     }
     location /front/embed.html {
-        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:";
+        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'";
         add_header Referrer-Policy "strict-origin-when-cross-origin";
 
         add_header X-Frame-Options "ALLOW";
diff --git a/docs/admin/backup.rst b/docs/admin/backup.rst
index d474678ae3ba48a5c0a51b9114200673148c894e..7578784dcdc585e32111272eacef1f762cf5d6bc 100644
--- a/docs/admin/backup.rst
+++ b/docs/admin/backup.rst
@@ -14,8 +14,8 @@ If you've followed the setup instructions in :doc:`../installation/docker`, here
 Multi-container installation
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Backup the db
-^^^^^^^^^^^^^
+Backup the database
+^^^^^^^^^^^^^^^^^^^
 
 On docker setups, you have to ``pg_dumpall`` in container ``funkwhale_postgres_1``:
 
@@ -47,8 +47,8 @@ On docker setups, the configuration file is located at the root level:
 Non-docker setup
 ----------------
 
-Backup the db
-^^^^^^^^^^^^^
+Backup the database
+^^^^^^^^^^^^^^^^^^^
 
 On non-docker setups, you have to ``pg_dump`` as user ``postgres``:
 
diff --git a/docs/admin/configuration.rst b/docs/admin/configuration.rst
index e68215e267604e7344de82b16283b6ea226882f0..7fad9a929817eb9324ce224431c93e2a43bd19c4 100644
--- a/docs/admin/configuration.rst
+++ b/docs/admin/configuration.rst
@@ -2,8 +2,9 @@ Instance configuration
 ======================
 
 General configuration is achieved using two type of settings:
-:ref:`environment variables <environment-variables>` and
-:ref:`instance settings <instance-settings>`.
+
+- :ref:`environment variables <environment-variables>` and
+- :ref:`instance settings <instance-settings>`.
 
 .. _environment-variables:
 
@@ -11,8 +12,7 @@ Environment variables
 ---------------------
 
 Those are located in your ``.env`` file, which you should have created
-during installation. A full list of available variables can be seen
-:ref:`below <environment-variables>`.
+during installation. A full list of available variables is given :ref:`below <environment-variables>`.
 
 Options from this file are heavily commented, and usually target lower level
 and technical aspects of your instance, such as database credentials.
@@ -20,7 +20,9 @@ and technical aspects of your instance, such as database credentials.
 .. note::
 
     You should restart all Funkwhale processes when you change the values
-    on environment variables.
+    on environment variables::
+        
+        sudo systemctl restart funkwhale.target
 
 
 .. note::
@@ -28,10 +30,11 @@ and technical aspects of your instance, such as database credentials.
     Some characters are unsafe to use in configuration variables that are URLs,
     such as the user and password in the database and SMTP sections.
     If those variables contain such characters, they must be urlencoded, for
-    instance using the following command:
-    ``python3 -c 'import urllib.parse; print(urllib.parse.quote_plus("p@ssword"))``
+    instance using the following command::
+
+        python3 -c 'import urllib.parse; print(urllib.parse.quote_plus("p@ssword"))
 
-    cf. https://github.com/joke2k/django-environ#using-unsafe-characters-in-urls
+    See as well https://github.com/joke2k/django-environ#using-unsafe-characters-in-urls
 
 .. _instance-settings:
 
@@ -186,21 +189,20 @@ User permissions
 
 Funkwhale's permission model works as follows:
 
-- Anonymous users cannot do anything unless configured specifically
-- Logged-in users can use the application, but cannot do things that affect
-  the whole instance
-- Superusers can do anything
+- Anonymous users cannot do anything unless configured specifically;
+- Logged-in users can use the application, but cannot do things that affect the whole instance;
+- Superusers can do anything.
 
 To make things more granular and allow some delegation of responsibility,
 superusers can grant specific permissions to specific users. Available
 permissions are:
 
 - **Manage instance-level settings**: users with this permission can edit instance
-  settings as described in :ref:`instance-settings`
+  settings as described in :ref:`instance-settings`;
 - **Manage library**: users with this permission can import new music in the
-  instance
+  instance;
 - **Manage library federation**: users with this permission can ask to federate with
-  other instances, and accept/deny federation requests from other instances
+  other instances, and accept/deny federation requests from other instances.
 
 There is no dedicated interface to manage users permissions, but superusers
 can login on the :doc:`Django's admin <django>` at ``/api/admin/`` and grant permissions
@@ -216,7 +218,7 @@ on ``https://yourinstanceurl/settings.json``.
 On typical deployments, this url returns a 404 error, which is simply ignored.
 
 Set-up
-------
+^^^^^^
 
 First, create the settings file:
 
@@ -244,11 +246,11 @@ If you are using nginx, add the following snippet to your vhost configuration::
         alias /srv/funkwhale/custom/settings.json;
     }
 
-On apache, add the following to your vhost configuration::
+On Apache, add the following to your vhost configuration::
 
     Alias /settings.json /srv/funkwhale/custom/settings.json
 
-Then reload your reverse proxy.
+Then, reload your reverse proxy.
 
 At this point, visiting ``https://yourinstanceurl/settings.json`` should serve the content
 of the settings.json file.
@@ -285,8 +287,8 @@ Theming
 
 To theme your Funkwhale instance, you need:
 
-1. A CSS file for your theme, that can be loaded by the front-end
-2. To update the value of ``additionalStylesheets`` in your settings.json file to point to your CSS file URL
+1. A CSS file for your theme, that can be loaded by the front-end;
+2. To update the value of ``additionalStylesheets`` in your settings.json file to point to your CSS file URL.
 
 .. code-block:: shell
 
@@ -311,7 +313,7 @@ On nginx, add the following snippet to your vhost config::
         alias /srv/funkwhale/custom;
     }
 
-On apache, use the following::
+On Apache, use the following::
 
     Alias /custom /srv/funkwhale/custom
 
diff --git a/docs/developers/debugging.rst b/docs/admin/debugging.rst
similarity index 100%
rename from docs/developers/debugging.rst
rename to docs/admin/debugging.rst
diff --git a/docs/admin/importing-music.rst b/docs/admin/importing-music.rst
index 0a0a97302013fcc32c986a811a4b7b0f3f09169e..5ba7a567d54f526fa7f3c51b13d519f17e4d0d49 100644
--- a/docs/admin/importing-music.rst
+++ b/docs/admin/importing-music.rst
@@ -93,8 +93,8 @@ following behaviour during import:
 Because those files are not managed by Funkwhale, we offer additional
 configuration options to ensure the webserver can serve them properly:
 
-- :ref:`setting-MUSIC_DIRECTORY_PATH`
-- :ref:`setting-MUSIC_DIRECTORY_SERVE_PATH`
+- :data:`MUSIC_DIRECTORY_PATH <config.settings.common.MUSIC_DIRECTORY_PATH>`
+- :data:`MUSIC_DIRECTORY_SERVING_PATH <config.settings.common.MUSIC_DIRECTORY_SERVE_PATH>`
 
 We recommend you symlink all your music directories into ``/srv/funkwhale/data/music``
 and run the `import_files` command from that directory. This will make it possible
diff --git a/docs/admin/index.rst b/docs/admin/index.rst
index 3ce9683c6500db50d27ff8d930ca6dace93dc2ed..1c22351f9789a99fe6054a6c06a1d091fa31bd88 100644
--- a/docs/admin/index.rst
+++ b/docs/admin/index.rst
@@ -15,7 +15,11 @@ Setup Guides
    configuration
    importing-music
    external-storages
+   optimization
+   backup
    migration
+   uninstall
+   ldap
 
 Administration
 --------------
@@ -29,10 +33,11 @@ Administration
    upgrading
    mrf
 
-Troubleshooting Issues
-----------------------
+Troubleshooting
+---------------
 
 .. toctree::
    :maxdepth: 2
 
    troubleshooting
+   debugging
diff --git a/docs/installation/ldap.rst b/docs/admin/ldap.rst
similarity index 63%
rename from docs/installation/ldap.rst
rename to docs/admin/ldap.rst
index dc5582f7dde4463ee9bfabad2345cac092d8c9c2..862b3040160c6f5be40b3da8002b96303d4b7ab4 100644
--- a/docs/installation/ldap.rst
+++ b/docs/admin/ldap.rst
@@ -7,9 +7,7 @@ Funkwhale supports LDAP through the Django LDAP authentication module and by set
 
 .. warning::
 
-    Note the following restrictions when using LDAP:
-
-        * LDAP-based users cannot change passwords inside the app.
+    Note that LDAP-based users cannot change their password inside the app.
 
 Dependencies
 ------------
@@ -21,23 +19,25 @@ Environment variables
 
 LDAP authentication is configured entirely through the environment variables. The following options enable the LDAP features:
 
-Basic features:
+Basic features
+^^^^^^^^^^^^^^
 
-* ``LDAP_ENABLED``: Set to ``True`` to enable LDAP support. Default: ``False``.
-* ``LDAP_SERVER_URI``: LDAP URI to the authentication server, e.g. ``ldap://my.host:389``.
-* ``LDAP_BIND_DN``: LDAP user DN to bind as to perform searches.
-* ``LDAP_BIND_PASSWORD``: LDAP user password for bind DN.
-* ``LDAP_SEARCH_FILTER``: The LDAP user filter, using ``{0}`` as the username placeholder, e.g. ``(|(cn={0})(mail={0}))``; uses standard LDAP search syntax. Default: ``(uid={0})``.
-* ``LDAP_START_TLS``: Set to ``True`` to enable LDAP StartTLS support. Default: ``False``.
-* ``LDAP_ROOT_DN``: The LDAP search root DN, e.g. ``dc=my,dc=domain,dc=com``; supports multiple entries in a space-delimited list, e.g. ``dc=users,dc=domain,dc=com dc=admins,dc=domain,dc=com``.
-* ``LDAP_USER_ATTR_MAP``: A mapping of Django user attributes to LDAP values, e.g. ``first_name:givenName, last_name:sn, username:cn, email:mail``. Default: ``first_name:givenName, last_name:sn, username:cn, email:mail``.
-* ``AUTH_LDAP_BIND_AS_AUTHENTICATING_USER``: Controls whether direct binding is used. Default: ``False``.
+- ``LDAP_ENABLED``: Set to ``True`` to enable LDAP support. Default: ``False``.
+- ``LDAP_SERVER_URI``: LDAP URI to the authentication server, e.g. ``ldap://my.host:389``.
+- ``LDAP_BIND_DN``: LDAP user DN to bind as to perform searches.
+- ``LDAP_BIND_PASSWORD``: LDAP user password for bind DN.
+- ``LDAP_SEARCH_FILTER``: The LDAP user filter, using ``{0}`` as the username placeholder, e.g. ``(|(cn={0})(mail={0}))``; uses standard LDAP search syntax. Default: ``(uid={0})``.
+- ``LDAP_START_TLS``: Set to ``True`` to enable LDAP StartTLS support. Default: ``False``.
+- ``LDAP_ROOT_DN``: The LDAP search root DN, e.g. ``dc=my,dc=domain,dc=com``; supports multiple entries in a space-delimited list, e.g. ``dc=users,dc=domain,dc=com dc=admins,dc=domain,dc=com``.
+- ``LDAP_USER_ATTR_MAP``: A mapping of Django user attributes to LDAP values, e.g. ``first_name:givenName, last_name:sn, username:cn, email:mail``. Default: ``first_name:givenName, last_name:sn, username:cn, email:mail``.
+- ``AUTH_LDAP_BIND_AS_AUTHENTICATING_USER``: Controls whether direct binding is used. Default: ``False``.
 
-Group features:
+Group features
+^^^^^^^^^^^^^^
 
-For details on these options, see `the Django documentation <https://django-auth-ldap.readthedocs.io/en/latest/groups.html>`_. Group configuration is disabled unless an ``LDAP_GROUP_DN`` is set. This is an advanced LDAP feature and most users should not need to configure these settings.
+For details on these options, see the `Django documentation <https://django-auth-ldap.readthedocs.io/en/latest/groups.html>`_. Group configuration is disabled unless an ``LDAP_GROUP_DN`` is set. This is an advanced LDAP feature and most users should not need to configure these settings.
 
-* ``LDAP_GROUP_DN``: The LDAP group search root DN, e.g. ``ou=groups,dc=domain,dc=com``.
-* ``LDAP_GROUP_FILTER``: The LDAP group filter, e.g. ``(objectClass=groupOfNames)``.
-* ``LDAP_REQUIRE_GROUP``: A group users must be a part of to authenticate, e.g. ``cn=enabled,ou=groups,dc=domain,dc=com``.
-* ``LDAP_DENY_GROUP``: A group users must not be a part of to authenticate, e.g. ``cn=disabled,ou=groups,dc=domain,dc=com``.
+- ``LDAP_GROUP_DN``: The LDAP group search root DN, e.g. ``ou=groups,dc=domain,dc=com``.
+- ``LDAP_GROUP_FILTER``: The LDAP group filter, e.g. ``(objectClass=groupOfNames)``.
+- ``LDAP_REQUIRE_GROUP``: A group users must be a part of to authenticate, e.g. ``cn=enabled,ou=groups,dc=domain,dc=com``.
+- ``LDAP_DENY_GROUP``: A group users must not be a part of to authenticate, e.g. ``cn=disabled,ou=groups,dc=domain,dc=com``.
diff --git a/docs/admin/migration.rst b/docs/admin/migration.rst
index 09d7efeca23c761d4af4c5d1f60362c6c287990c..cc8fc3127f4844fb73c4976431938e951528d258 100644
--- a/docs/admin/migration.rst
+++ b/docs/admin/migration.rst
@@ -6,6 +6,8 @@ existing Funkwhale setup to a new server. This can be helpful
 if you need to boost resources or if you wish to use a different
 hosting platform.
 
+In this guide, the existing Funkwhale setup is called the origin server, and the new setup the destination server.
+
 Requirements
 ------------
 
@@ -18,67 +20,83 @@ following:
 Non-Docker
 ----------
 
-- On the target server, run through the :doc:`installation steps<../installation/debian>` but skip the Database setup steps
-- Stop all funkwhale related services on the destination server
-- On the original server, create a database backup
+On the destination server, run through the :doc:`installation steps<../installation/debian>` with the exception of the following points:
+
+- Do not enable the extensions ``unaccent`` and ``citext`` when setting up the database;
+- Do not initialize the database by applying the migrate command;
+- Do not create an admin account.
+
+Stop all funkwhale related services on the destination server:
 
 .. code-block:: shell
 
-    sudo -u funkwhale pg_dump -d funkwhale > "db.dump"
+    sudo systemctl stop funkwhale.target
 
-- On the destination server, use rsync to copy the contents of `/srv/funwkhale/data/media/music` and `/srv/funkwhale/data/music` from the original server
+On the origin server, create a database backup:
 
 .. code-block:: shell
 
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/data/media/ /srv/funkwhale/data/media/
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/data/music/ /srv/funkwhale/data/music/
+    sudo -u funkwhale pg_dump -d funkwhale > "db.dump"
 
-- Copy your .env file and database backup from your original server
+On the destination server, use rsync to fetch the contents of ``/srv/funwkhale/data/media/music/`` and ``/srv/funkwhale/data/media/`` from the origin server, as well as the database dump and the ``.env`` file:
 
 .. code-block:: shell
 
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/config/.env /srv/funkwhale/config/
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/db.dump /srv/funkwhale/
+    origin = <origin server IP/hostname>
+    username = <your username>
+
+    rsync -a $username@$origin:/srv/funkwhale/data/media/ /srv/funkwhale/data/media/
+    rsync -a $username@$origin:/srv/funkwhale/data/music/ /srv/funkwhale/data/music/
 
-- Restore the database dump
+    rsync -a $username@$origin:/srv/funkwhale/config/.env /srv/funkwhale/config/
+    rsync -a $username@$origin:/srv/funkwhale/db.dump /srv/funkwhale/
+
+On the destination server, restore the database dump:
 
 .. code-block:: shell
 
-    sudo -u funkwhale pg_restore -d funkwhale db.dump
+    sudo psql -d funkwhale db.dump
+
+Once the database has been restored, follow the database migration steps from the non-docker installation guide to complete the installation on the destination server.
 
-- Once the database has been restored, follow the database migration steps from the guide to complete the installation
-- Ensure that all DNS changes have been made and start the services
+Ensure that all DNS changes have been made and start the services:
+
+.. code-block:: shell
+
+    sudo systemctl start funkwhale.target
 
 Docker
 ------
 
-- On the target server, run through the :doc:`installation steps<../installation/docker>` but skip the `docker-compose run --rm api python manage.py migrate` step
-- Stop all funkwhale related containers on the destination server
-- On the original server, create a database backup
+On the destination server, run through the :doc:`installation steps<../installation/docker>` but skip the ``docker-compose run --rm api python manage.py migrate`` step.
+
+Stop all funkwhale related containers on the destination server.
+
+On the origin server, create a database backup:
 
 .. code-block:: shell
 
     docker exec -t funkwhale_postgres_1 pg_dumpall -c -U postgres > "db.dump"
 
-- On the destination server, use rsync to copy the contents of `/srv/funwkhale/data/media/music` and `/srv/funkwhale/data/music` from the original server
+On the destination server, use rsync to fetch the contents of ``/srv/funwkhale/data/media/music`` and ``/srv/funkwhale/data/media`` from the origin server, as well as the database dump nd the ``.env`` file:
 
 .. code-block:: shell
 
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/data/media/ /srv/funkwhale/data/media/
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/data/music/ /srv/funkwhale/data/music/
-
-- Copy your .env file and database backup from your original server
+    origin = <origin server IP/hostname>
+    username = <your username>
 
-.. code-block:: shell
+    rsync -a $username@$origin:/srv/funkwhale/data/media/ /srv/funkwhale/data/media/
+    rsync -a $username@$origin:/srv/funkwhale/data/music/ /srv/funkwhale/data/music/
 
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/.env /srv/funkwhale/
-    rsync -a <your username>@<original server IP/hostname>:/srv/funkwhale/db.dump /srv/funkwhale/
+    rsync -a $username@$origin:/srv/funkwhale/.env /srv/funkwhale/
+    rsync -a $username@$origin:/srv/funkwhale/db.dump /srv/funkwhale/
 
-- Restore the database dump
+Restore the database dump:
 
 .. code-block:: shell
 
     docker exec -i funkwhale_postgres_1 pg_restore -c -U postgres -d postgres < "db.dump"
 
-- Once the database has been restored, run the migrations
-- Ensure that all DNS changes have been made and start the services
\ No newline at end of file
+Once the database has been restored, run the migrations following the docker installation guide. 
+
+Ensure that all DNS changes have been made and start the services.
\ No newline at end of file
diff --git a/docs/admin/mrf.rst b/docs/admin/mrf.rst
index 1d905e72b10f03f925c72dbb38b62fd790134412..32effdc8d74b416f7e3259c91d78ebab512e99d8 100644
--- a/docs/admin/mrf.rst
+++ b/docs/admin/mrf.rst
@@ -13,8 +13,8 @@ of registered policies, in turn. Each policy can mutate the message, leave it as
 
 Some of our built-in moderation tools are actually implemented as a MRF policy, e.g:
 
-- Allow-list, when checking incoming messages (`code <https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/moderation/mrf_policies.py>`_)
-- Domain and user blocking, when checking incoming messages (`code <https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/federation/mrf_policies.py>`_)
+- Allow-list, when checking incoming messages (`code <https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/moderation/mrf_policies.py>`__)
+- Domain and user blocking, when checking incoming messages (`code <https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/federation/mrf_policies.py>`__)
 
 .. note::
 
diff --git a/docs/installation/optimization.rst b/docs/admin/optimization.rst
similarity index 100%
rename from docs/installation/optimization.rst
rename to docs/admin/optimization.rst
diff --git a/docs/admin/troubleshooting.rst b/docs/admin/troubleshooting.rst
index a8c6f2546cad8b4ca1a456648d5ef9727b99d07c..59acf2c17bcff44c265eba5adfbcda599a5d0d2a 100644
--- a/docs/admin/troubleshooting.rst
+++ b/docs/admin/troubleshooting.rst
@@ -50,12 +50,12 @@ Diagnostic tools:
 Common problems
 ***************
 
-Instance work properly, but audio files are not served (404 error)
+Instance works properly, but audio files are not served (404 error)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 - If you're using docker, ensure the ``MEDIA_ROOT`` variable is commented in your env file
 - Ensure the ``_protected/media`` block points toward the path where media files are stored (``/srv/funkwhale/data/media``, by default)
-- If you're using in-place import, ensure :ref:`setting-MUSIC_DIRECTORY_PATH`, :ref:`setting-MUSIC_DIRECTORY_SERVE_PATH` and :ref:`setting-REVERSE_PROXY_TYPE` are configured properly, and that the files are readable by the webserver
+- If you're using in-place import, ensure :data:`MUSIC_DIRECTORY_PATH <config.settings.common.MUSIC_DIRECTORY_PATH>`, :data:`MUSIC_DIRECTORY_SERVE_PATH <config.settings.common.MUSIC_DIRECTORY_SERVE_PATH>` and :data:`REVERSE_PROXY_TYPE <config.settings.common.REVERSE_PROXY_TYPE>` are configured properly, and that the files are readable by the webserver
 
 Weakref error when running ``python manage.py <command>``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/admin/uninstall.rst b/docs/admin/uninstall.rst
new file mode 100644
index 0000000000000000000000000000000000000000..df85edec4505397020ebaef55138787a88084a03
--- /dev/null
+++ b/docs/admin/uninstall.rst
@@ -0,0 +1,89 @@
+Uninstall Funkwhale
+===================
+
+The following instructions helps you remove Funkwhale from your server, for instance after migrating to another server, or if you do not want to use Funkwhale anymore.
+
+.. warning::
+
+    The following instructions cannot be undone and might result in loss of data. If necessary, please make a backup of your server following the :doc:`backup instructions<backup>`.
+
+    Especially, it must be noted that:
+
+    - Remote content hosted on an S3 or S3-compatible server will not be removed.
+    - In place imports will not be removed, provided they are located outside ``/srv/funkwhale/`` 
+
+.. note::
+
+    These instructions apply only for the manual installation on Debian or Arch Linux. It matches the default setup.
+
+First, stop the all funkwhale related services:
+
+.. code-block:: shell
+
+    sudo systemctl stop funkwhale.target
+
+Remove the reverse proxy configuration data and reload the reverse proxy.
+
+If you are using nginx:
+
+.. code-block:: shell
+
+    sudo rm /etc/nginx/sites-enabled/funkwhale.conf    
+    sudo rm /etc/nginx/sites-available/funkwhale.conf
+    sudo rm /etc/nginx/funkwhale_proxy.conf
+
+    sudo systemctl reload nginx
+
+If you are using Apache2:
+
+.. code-block:: shell
+
+    sudo rm /etc/apache2/sites-enabled/funkwhale.conf    
+    sudo rm /etc/apache2/sites-available/funkwhale.conf
+
+    sudo service apache2 restart
+
+Remove the systemd services:
+
+.. code-block:: shell
+
+    sudo systemctl disable funkwhale-server
+    sudo systemctl disable funkwhale-worker
+    sudo systemctl disable funkwhale-beat
+
+    sudo rm /etc/systemd/system/funkwhale-server.service
+    sudo rm /etc/systemd/system/funkwhale-worker.service
+    sudo rm /etc/systemd/system/funkwhale-beat.service
+    sudo rm /etc/systemd/system/funkwhale.target
+
+    sudo systemctl daemon-reload
+    sudo systemctl reset-failed
+
+Then, remove the database:
+
+.. code-block:: shell
+
+    sudo -u postgres psql -c 'DROP DATABASE funkwhale;'
+    sudo -u postgres psql -c 'DROP USER funkwhale;'
+
+Finally, remove the user ``funkwhale`` and all funkwhale related data, including the server and the data:
+
+.. code-block:: shell
+
+    sudo userdel -r funkwhale
+
+.. warning::
+
+    The last command will remove ``/srv/funkwhale/``. On the default setup, this directory contains all user data. Please proceed cautiously!
+
+    However, it must be noted that:
+
+    - Remote content hosted on an S3 or S3-compatible server will not be removed.
+    - In place imports will not be removed, provided they are not located in the directory ``/srv/funkwhale/``
+
+.. note::
+
+    If relevant, you might also want to:
+
+    - remove the SSL certificates;
+    - remove the corresponding DNS entries.
\ No newline at end of file
diff --git a/docs/api/definitions.yml b/docs/api/definitions.yml
index b9e1eeb4236a6195bd1ab1405270097fcc0ccdb0..aec879c8893d67911bac9e86d75326189207812f 100644
--- a/docs/api/definitions.yml
+++ b/docs/api/definitions.yml
@@ -738,7 +738,7 @@ Me:
         email:
           type: "string"
           format: "email"
-          description: Email address associated with the account
+          description: E-mail address associated with the account
           example: "alice@email.provider"
         is_staff:
           type: "boolean"
diff --git a/docs/backup.rst b/docs/backup.rst
deleted file mode 100644
index d474678ae3ba48a5c0a51b9114200673148c894e..0000000000000000000000000000000000000000
--- a/docs/backup.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-Backup your Funkwhale instance
-==============================
-
-.. note::
-
-    Before upgrading your instance, we strongly advise you to make at least a database backup. Ideally, you should make a full backup, including the database and the media files.
-
-
-Docker setup
-------------
-
-If you've followed the setup instructions in :doc:`../installation/docker`, here is the backup path:
-
-Multi-container installation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Backup the db
-^^^^^^^^^^^^^
-
-On docker setups, you have to ``pg_dumpall`` in container ``funkwhale_postgres_1``:
-
-.. code-block:: shell
-
-   docker exec -t funkwhale_postgres_1 pg_dumpall -c -U postgres > dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql
-
-Backup the media files
-^^^^^^^^^^^^^^^^^^^^^^
-
-To backup docker data volumes, as the volumes are bound mounted to the host, the ``rsync`` way would go like this:
-
-.. code-block:: shell
-
-    rsync -avzhP /srv/funkwhale/data/media /path/to/your/backup/media
-    rsync -avzhP /srv/funkwhale/data/music /path/to/your/backup/music
-
-
-Backup the configuration files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-On docker setups, the configuration file is located at the root level:
-
-.. code-block:: shell
-
-    rsync -avzhP /srv/funkwhale/.env /path/to/your/backup/.env
-
-
-Non-docker setup
-----------------
-
-Backup the db
-^^^^^^^^^^^^^
-
-On non-docker setups, you have to ``pg_dump`` as user ``postgres``:
-
-.. code-block:: shell
-
-   sudo -u postgres -H pg_dump funkwhale > /path/to/your/backup/dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql
-
-Backup the media files
-^^^^^^^^^^^^^^^^^^^^^^
-
-A simple way to backup your media files is to use ``rsync``:
-
-.. code-block:: shell
-
-    rsync -avzhP /srv/funkwhale/data/media /path/to/your/backup/media
-    rsync -avzhP /srv/funkwhale/data/music /path/to/your/backup/music
-
-Backup the configuration files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: shell
-
-    rsync -avzhP /srv/funkwhale/config/.env /path/to/your/backup/.env
-
-.. note::
-   You may also want to backup your proxy configuration file.
-
-   For frequent backups, you may want to use deduplication and compression to keep the backup size low. In this case, a tool like ``borg`` will be more appropriate.
diff --git a/docs/cli/examples.rst b/docs/cli/examples.rst
deleted file mode 100644
index f57f3ae5509dfcfcb6f71b79ec799ea1ebbb9bf4..0000000000000000000000000000000000000000
--- a/docs/cli/examples.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-Funkwhale CLI examples
-======================
-
-Uploading local files
----------------------
-
-**Goal**: create a library and upload all MP3 files from ``~/Music`` to it
-
-**Commands**::
-
-    funkwhale libraries create --name "My awesome library" --visibility me
-    # copy the returned UUID
-    funkwhale uploads create <UUID> ~/Music/**/*.mp3
-
-
-Favorite an entire album
-------------------------
-
-**Goal**: retrieve all the tracks from an album and add these to your favorites
-
-**Commands**::
-
-    # retrieve the album ID
-    funkwhale albums ls "The Slip"
-
-    # Copy the ID, then retrieve 100 pages of tracks from that album
-    # get only the IDs and pipe those to the favorite creation command
-    funkwhale tracks ls -f "album=<ID>" --ids --limit 100 \
-        | xargs funkwhale favorites tracks create
-
-
-Mirror an artist discography locally
-------------------------------------
-
-**Goal**: Download the discography of an artist locally, in the ``~/Music`` directory, in an ``Artist/Album/Track`` folder hierarchy
-
-**Commands**::
-
-    # retrieve the artist ID
-    funkwhale artists ls "Nine Inch Nails"
-
-    # Copy the ID, then retrieve 100 pages of tracks from that artist
-    # get only the IDs and pipe those to the download command
-    funkwhale tracks ls -f "artist=<ID>" --ids --limit 100 \
-        | xargs funkwhale tracks download \
-            -f mp3 -d ~/Music -t "{artist}/{album}/{title}.{extension}"
-
-
-Open a remote album in VLC
---------------------------
-
-**Goal**: Variation of the previous example, but instead of downloading an artist discography, we listen to an album in VLC
-
-**Commands**::
-
-    # retrieve the album ID
-    funkwhale albums ls "The Slip"
-
-    # Copy the ID, then retrieve 100 pages of tracks from that album
-    # get only the IDs, download the corresponding tracks and pipe the audio
-    # directly to VLC
-    funkwhale tracks ls -f "album=<ID>" --ids --limit 100 \
-        | xargs funkwhale tracks download \
-        | vlc -
diff --git a/docs/conf.py b/docs/conf.py
index 5ac3127be72e35e02342040ce84546609b6de215..40af323abb9abcd704b2cc06af069e852a0a3406 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -64,8 +64,8 @@ master_doc = "index"
 # General information about the project.
 year = datetime.datetime.now().year
 project = "funkwhale"
-copyright = "{}, Eliot Berriot".format(year)
-author = "Eliot Berriot"
+copyright = "{}, The Funkwhale Collective".format(year)
+author = "The Funkwhale Collective"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -107,7 +107,7 @@ html_theme = "sphinx_rtd_theme"
 # further.  For a list of options available for each theme, see the
 # documentation.
 #
-html_theme_options = {"gitlab_url": "https://dev.funkwhale.audio/funkwhale/funkwhale"}
+#html_theme_options = {}
 html_context = {
     "display_gitlab": True,
     "gitlab_host": "dev.funkwhale.audio",
@@ -115,13 +115,15 @@ html_context = {
     "gitlab_user": "funkwhale",
     "gitlab_version": "master",
     "conf_py_path": "/docs/",
+    "gitlab_url": "https://dev.funkwhale.audio/funkwhale/funkwhale",
 }
 html_logo = "logo.svg"
+html_favicon = "../front/public/favicon.png"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
+# html_static_path = ["_static"]
 
 # -- Options for HTMLHelp output ------------------------------------------
 
@@ -190,6 +192,13 @@ redirect_files = [
     ("upgrading/index.html", "../admin/upgrading.html"),
     ("upgrading/0.17.html", "../admin/0.17.html"),
     ("users/django.html", "../admin/django.html"),
+    ("cli/index.html", "../users/cli.html"),
+    ("cli/examples.html", "../users/cli.html#examples"),
+    ("installation/ldap.html", "../admin/ldap.html"),
+    ("installation/optimization.html", "../admin/optimization.html"),
+    ("installation/external_dependencies.html", "debian.html"),
+    ("installation/systemd.html", "debian.html#systemd-unit-file"),
+    ("backup.html", "../admin/backup.html"),
 ]
 
 # Generate redirect template
diff --git a/docs/developers/authentication.rst b/docs/developers/authentication.rst
index 0d32139c0ed3eb80b99a7c2ec5a81aee700d1cbb..409e25124eaf733b2cb1fdce062f1dbf3ae40e1a 100644
--- a/docs/developers/authentication.rst
+++ b/docs/developers/authentication.rst
@@ -65,33 +65,33 @@ Having the generic ``read`` or ``write`` scope give you the corresponding access
 This is the list of OAuth scopes that third-party applications can request:
 
 
-+-------------------------------------------+---------------------------------------------------+
-| Scope                                     | Description                                       |
-+===========================================+===================================================+
-| ``read``                                  | Read-only access to all data                      |
-|                                           | (equivalent to all ``read:*`` scopes)             |
-+-------------------------------------------+---------------------------------------------------+
-| ``write``                                 | Write-only access to all data                     |
-|                                           | (equivalent to all ``write:*`` scopes)            |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:profile``                  | Access to profile data (email, username, etc.)    |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:libraries``                | Access to library data (uploads, libraries        |
-|                                           | tracks, albums, artists...)                       |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:favorites``                | Access to favorites                               |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:listenings``               | Access to history                                 |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:follows``                  | Access to followers                               |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:playlists``                | Access to playlists                               |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:radios``                   | Access to radios                                  |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:filters``                  | Access to content filters                         |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:notifications``            | Access to notifications                           |
-+-------------------------------------------+---------------------------------------------------+
-| ``<read/write>:edits``                    | Access to metadata edits                          |
-+-------------------------------------------+---------------------------------------------------+
++-------------------------------------------+------------------------------------------------------------+
+| Scope                                     | Description                                                |
++===========================================+============================================================+
+| ``read``                                  | Read-only access to all data                               |
+|                                           | (equivalent to all ``read:*`` scopes)                      |
++-------------------------------------------+------------------------------------------------------------+
+| ``write``                                 | Write-only access to all data                              |
+|                                           | (equivalent to all ``write:*`` scopes)                     |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:profile``                  | Access to profile data (e-mail address, username, etc.)    |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:libraries``                | Access to library data (uploads, libraries                 |
+|                                           | tracks, albums, artists...)                                |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:favorites``                | Access to favorites                                        |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:listenings``               | Access to history                                          |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:follows``                  | Access to followers                                        |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:playlists``                | Access to playlists                                        |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:radios``                   | Access to radios                                           |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:filters``                  | Access to content filters                                  |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:notifications``            | Access to notifications                                    |
++-------------------------------------------+------------------------------------------------------------+
+| ``<read/write>:edits``                    | Access to metadata edits                                   |
++-------------------------------------------+------------------------------------------------------------+
diff --git a/docs/developers/index.rst b/docs/developers/index.rst
index f214c78196edc6f14c7b13b75d630d56d53d5af9..9dc71690143f49ae57449579902e9051a8000277 100644
--- a/docs/developers/index.rst
+++ b/docs/developers/index.rst
@@ -4,9 +4,6 @@ Developer documentation
 This documentation is targeted primarily at developers who want to understand
 how Funkwhale works and how to build apps that integrate with Funkwhale's ecosystem.
 
-Reference
----------
-
 .. toctree::
    :maxdepth: 2
 
diff --git a/docs/developers/plugins.rst b/docs/developers/plugins.rst
index f9f537479d4778eed41630f505e7ce20b89ff487..abfd58dc1f0f0d3116e8a72adc1262e5c3467d84 100644
--- a/docs/developers/plugins.rst
+++ b/docs/developers/plugins.rst
@@ -160,6 +160,6 @@ Filters reference
 
 .. autodata:: config.plugins.PLUGINS_DEPENDENCIES
 .. autodata:: config.plugins.PLUGINS_APPS
-.. autodata:: config.plugins.PLUGINSMIDDLEWARES_BEFORE_DEPENDENCIES
+.. autodata:: config.plugins.MIDDLEWARES_BEFORE
 .. autodata:: config.plugins.MIDDLEWARES_AFTER
 .. autodata:: config.plugins.URLS
diff --git a/docs/installation/debian.rst b/docs/installation/debian.rst
index ef89ef5d11b3b55fd1e6ca12bf260f85590775b4..b91ee0dba753d0cae62192424b8da647007b2ac6 100644
--- a/docs/installation/debian.rst
+++ b/docs/installation/debian.rst
@@ -3,15 +3,30 @@ Debian and Arch Linux installation
 
 .. note::
 
-    This guide targets Debian 9 (Stretch), which is the latest Debian, as well as Arch Linux.
+    This guide targets Debian 10 (Buster), which is the latest version available, as well as Arch Linux.
 
-External dependencies
----------------------
+Cache setup (Redis)
+-------------------
 
-The guides will focus on installing Funkwhale-specific components and
-dependencies. However, Funkwhale requires a
-:doc:`few external dependencies <./external_dependencies>` for which
-documentation is outside of this document scope.
+Funkwhale requires a cache server:
+
+- to make the whole system faster, by caching network requests or database queries;
+- to handle asynchronous tasks such as music import.
+
+On Debian-like distributions, a redis package is available, and you can
+install it:
+
+.. code-block:: shell
+
+    sudo apt-get install redis-server
+
+On Arch Linux and its derivatives:
+
+.. code-block:: shell
+
+    sudo pacman -S redis
+
+This should be enough to have your redis server set up.
 
 Install system dependencies
 ---------------------------
@@ -21,8 +36,9 @@ On Debian-like systems, you can install them using:
 .. code-block:: shell
 
     sudo apt-get update
-    # Install dependencies
-    sudo apt-get install curl python3-pip python3-venv git unzip libldap2-dev libsasl2-dev gettext-base zlib1g-dev libffi-dev libssl-dev
+    # Install system dependencies
+    sudo apt-get install curl python3-pip python3-venv git unzip libldap2-dev libsasl2-dev gettext-base zlib1g-dev libffi-dev libssl-dev libxml2-dev libxslti1-dev
+
     # Funkwhale dependencies
     sudo apt install build-essential ffmpeg libjpeg-dev libmagic-dev libpq-dev postgresql-client python3-dev make
 
@@ -30,13 +46,19 @@ On Arch Linux and its derivatives:
 
 .. code-block:: shell
 
-    # Install dependencies
+    # Install system dependencies
     sudo pacman -S curl python-pip python-virtualenv git unzip
+
     # Funkwhale dependencies
     sudo pacman -S curl file ffmpeg libjpeg-turbo libpqxx python libldap libsasl
 
-Layout
--------
+External Authentication (LDAP)
+------------------------------
+
+LDAP support requires some additional dependencies to enable. On the OS level both ``libldap2-dev`` and ``libsasl2-dev`` are required, and the Python modules ``python-ldap`` and ``python-django-auth-ldap`` must be installed. These dependencies are all included in the ``requirements.*`` files so deploying with those will install these dependencies by default. However, they are not required unless LDAP support is explicitly enabled. See :doc:`../admin/ldap` for more details.
+
+Installation structure
+----------------------
 
 All Funkwhale-related files will be located under ``/srv/funkwhale`` apart
 from database files and a few configuration files. We will also have a
@@ -77,24 +99,23 @@ Create the aforementioned directories:
 
 The ``virtualenv`` directory is a bit special and will be created separately.
 
-Download latest Funkwhale release
-----------------------------------
+Download the latest Funkwhale release
+-------------------------------------
 
 Funkwhale is splitted in two components:
 
-1. The API, which will handle music storage and user accounts
-2. The frontend, that will simply connect to the API to interact with its data
+1. The API, which will handle music storage and user accounts;
+2. The frontend, that will simply connect to the API to interact with its data.
 
 Those components are packaged in subsequent releases, such as 0.1, 0.2, etc.
 You can browse the :doc:`changelog </changelog>` for a list of available releases
 and pick the one you want to install, usually the latest one should be okay.
 
-In this guide, we'll assume you want to install the latest version of Funkwhale,
-which is |version|:
+In this guide, we will assume you want to install the latest version of Funkwhale, which is |version|:
 
-First, we'll download the latest api release.
+First, we will download the latest api release:
 
-.. parsed-literal::
+.. code-block:: shell
 
     curl -L -o "api-|version|.zip" "https://dev.funkwhale.audio/funkwhale/funkwhale/-/jobs/artifacts/|version|/download?job=build_api"
     unzip "api-|version|.zip" -d extracted
@@ -102,9 +123,9 @@ First, we'll download the latest api release.
     rm -rf extracted
 
 
-Then we'll download the frontend files:
+Then we will download the frontend files:
 
-.. parsed-literal::
+.. code-block:: shell
 
     curl -L -o "front-|version|.zip" "https://dev.funkwhale.audio/funkwhale/funkwhale/-/jobs/artifacts/|version|/download?job=build_front"
     unzip "front-|version|.zip" -d extracted
@@ -114,7 +135,7 @@ Then we'll download the frontend files:
 .. note::
 
     You can also choose to get the code directly from the git repo. In this
-    case, run
+    case, run::
 
         cd /srv
 
@@ -124,7 +145,7 @@ Then we'll download the frontend files:
 
     The above clone command uses the master branch instead of the default develop branch, as master is stable and more suited for production setups.
 
-    You'll also need to re-create the folders we make earlier:
+    You'll also need to re-create the folders we make earlier::
 
         mkdir -p config data/static data/media data/music front
 
@@ -158,9 +179,7 @@ This will result in a ``virtualenv`` directory being created in
 ``/srv/funkwhale/virtualenv``.
 
 In the rest of this guide, we'll need to activate this environment to ensure
-dependencies are installed within it, and not directly on your host system.
-
-This is done with the following command:
+dependencies are installed within it, and not directly on your host system. This is done with the following command:
 
 .. code-block:: shell
 
@@ -195,7 +214,7 @@ Download the sample environment file:
 
 .. note::
 
-    if you used git to get the latest version of the code earlier, you can instead do
+    if you used git to get the latest version of the code earlier, you can instead do::
 
         cp /srv/funkwhale/deploy/env.prod.sample /srv/funkwhale/config/.env
 
@@ -215,13 +234,68 @@ configuration options are mentioned at the top of the file.
 Paste the secret key you generated earlier at the entry
 ``DJANGO_SECRET_KEY`` and populate the ``DATABASE_URL``
 and ``CACHE_URL`` values based on how you configured
-your PostgreSQL and Redis servers in
-:doc:`external dependencies <./external_dependencies>`.
+your PostgreSQL and Redis servers in.
 
 Database setup
----------------
+--------------
 
-You should now be able to import the initial database structure:
+Funkwhale requires a PostgreSQL database to work properly. Please refer
+to the `PostgreSQL documentation <https://www.postgresql.org/download/>`_
+for installation instructions specific to your os.
+
+On Debian-like systems, you would install the database server like this:
+
+.. code-block:: shell
+
+    sudo apt-get install postgresql postgresql-contrib
+
+On Arch Linux and its derivatives:
+
+.. code-block:: shell
+
+    sudo pacman -S postgresql
+
+On Arch Linux, you'll also need to initialize the database. See `the Arch Linux wiki <https://wiki.archlinux.org/index.php/Postgresql#Initial_configuration>`_.
+
+The remaining steps are heavily inspired from `this Digital Ocean guide <https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04>`_.
+
+Open a database shell:
+
+.. code-block:: shell
+
+    sudo -u postgres psql
+
+Create the project database and user:
+
+.. code-block:: shell
+
+    CREATE DATABASE funkwhale WITH ENCODING 'utf8';
+    CREATE USER funkwhale;
+    GRANT ALL PRIVILEGES ON DATABASE funkwhale TO funkwhale;
+
+.. warning::
+
+    It's important that you use utf-8 encoding for your database,
+    otherwise you'll end up with errors and crashes later on when dealing
+    with music metadata that contains non-ascii chars.
+
+Assuming you already have :ref:`created your funkwhale user <create-funkwhale-user>`,
+you should now be able to open a postgresql shell:
+
+.. code-block:: shell
+
+    sudo -u funkwhale -H psql
+
+Unless you give a superuser access to the database user, you should also
+enable some extensions on your database server, as those are required
+for Funkwhale to work properly:
+
+.. code-block:: shell
+
+    sudo -u postgres psql funkwhale -c 'CREATE EXTENSION "unaccent";'
+    sudo -u postgres psql funkwhale -c 'CREATE EXTENSION "citext";'
+
+Now that the database has been created, import the initial database structure using the virtualenv created before:
 
 .. code-block:: shell
 
@@ -246,7 +320,7 @@ This will create the required tables and rows.
 Create an admin account
 -----------------------
 
-You can then create your first user account:
+Using the virtualenv created before, create your first user account:
 
 .. code-block:: shell
 
@@ -274,9 +348,59 @@ in your ``.env`` file.
 Systemd unit file
 ------------------
 
-See :doc:`./systemd`.
+.. note::
+
+    All the command lines below should be executed as root.
+
+Systemd offers a convenient way to manage your Funkwhale instance if you're
+not using docker. We'll see how to setup systemd to properly start a Funkwhale instance.
+
+First, download the sample unitfiles:
+
+.. parsed-literal::
+
+    sudo curl -L -o "/etc/systemd/system/funkwhale.target" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale.target"
+    sudo curl -L -o "/etc/systemd/system/funkwhale-server.service" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-server.service"
+    sudo curl -L -o "/etc/systemd/system/funkwhale-worker.service" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-worker.service"
+    sudo curl -L -o "/etc/systemd/system/funkwhale-beat.service" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-beat.service"
+
+This will download three unitfiles:
+
+- ``funkwhale-server.service`` to launch the Funkwhale web server;
+- ``funkwhale-worker.service`` to launch the Funkwhale task worker;
+- ``funkwhale-beat.service`` to launch the Funkwhale task beat (this is for recurring tasks);
+- ``funkwhale.target`` to easily stop and start all of the services at once.
+
+You can of course review and edit them to suit your deployment scenario
+if needed, but the defaults should be fine.
+
+Once the files are downloaded, reload systemd:
+
+.. code-block:: shell
+
+    sudo systemctl daemon-reload
+
+And start the services:
+
+.. code-block:: shell
+
+    sudo systemctl start funkwhale.target
+
+To ensure all Funkwhale processes are started automatically after a reboot, run:
+
+.. code-block:: shell
+
+    sudo systemctl enable funkwhale-server
+    sudo systemctl enable funkwhale-worker
+    sudo systemctl enable funkwhale-beat
+
+You can check the statuses of all processes like this:
+
+.. code-block:: shell
+
+    sudo systemctl status funkwhale-\*
 
 Reverse proxy setup
 --------------------
 
-See :ref:`reverse-proxy <reverse-proxy-setup>`.
+See :ref:`Reverse proxy <reverse-proxy-setup>`.
diff --git a/docs/installation/external_dependencies.rst b/docs/installation/external_dependencies.rst
deleted file mode 100644
index 6925eaf729f11cc7d0561fe0f2e2ba6ff4d8e2d3..0000000000000000000000000000000000000000
--- a/docs/installation/external_dependencies.rst
+++ /dev/null
@@ -1,99 +0,0 @@
-External dependencies
-=====================
-
-
-.. note::
-
-    Those dependencies are handled automatically if you are
-    :doc:`deploying using docker <./docker>`
-
-Database setup (PostgreSQL)
----------------------------
-
-Funkwhale requires a PostgreSQL database to work properly. Please refer
-to the `PostgreSQL documentation <https://www.postgresql.org/download/>`_
-for installation instructions specific to your os.
-
-On Debian-like systems, you would install the database server like this:
-
-.. code-block:: shell
-
-    sudo apt-get install postgresql postgresql-contrib
-
-On Arch Linux and its derivatives:
-
-.. code-block:: shell
-
-    sudo pacman -S postgresql
-
-On Arch, you'll also need to initialize the database. See `the Arch Linux wiki <https://wiki.archlinux.org/index.php/Postgresql#Initial_configuration>`_.
-
-The remaining steps are heavily inspired from `this Digital Ocean guide <https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04>`_.
-
-Open a database shell:
-
-.. code-block:: shell
-
-    sudo -u postgres psql
-
-Create the project database and user:
-
-.. code-block:: shell
-
-    CREATE DATABASE "funkwhale"
-      WITH ENCODING 'utf8';
-    CREATE USER funkwhale;
-    GRANT ALL PRIVILEGES ON DATABASE funkwhale TO funkwhale;
-
-.. warning::
-
-    It's important that you use utf-8 encoding for your database,
-    otherwise you'll end up with errors and crashes later on when dealing
-    with music metadata that contains non-ascii chars.
-
-
-Assuming you already have :ref:`created your funkwhale user <create-funkwhale-user>`,
-you should now be able to open a postgresql shell:
-
-.. code-block:: shell
-
-    sudo -u funkwhale -H psql
-
-Unless you give a superuser access to the database user, you should also
-enable some extensions on your database server, as those are required
-for Funkwhale to work properly:
-
-.. code-block:: shell
-
-    sudo -u postgres psql funkwhale -c 'CREATE EXTENSION "unaccent";'
-    sudo -u postgres psql funkwhale -c 'CREATE EXTENSION "citext";'
-
-
-Cache setup (Redis)
--------------------
-
-Funkwhale also requires a cache server:
-
-- To make the whole system faster, by caching network requests or database
-  queries
-- To handle asynchronous tasks such as music import
-
-On Debian-like distributions, a redis package is available, and you can
-install it:
-
-.. code-block:: shell
-
-    sudo apt-get install redis-server
-
-On Arch Linux and its derivatives:
-
-.. code-block:: shell
-
-    sudo pacman -S redis
-
-This should be enough to have your redis server set up.
-
-External Authentication (LDAP)
-------------------------------
-
-LDAP support requires some additional dependencies to enable. On the OS level both ``libldap2-dev`` and ``libsasl2-dev`` are required, and the Python modules ``python-ldap`` and ``python-django-auth-ldap`` must be installed. These dependencies are all included in the ``requirements.*`` files so deploying with those will install these dependencies by default. However, they are not required unless LDAP support is explicitly enabled. See :doc:`./ldap` for more details.
diff --git a/docs/installation/index.rst b/docs/installation/index.rst
index 08f521781b0e197a37806f284b57ec9e2bef2482..ce9a770317ac43d906eb68748f6016f5fba1d2ae 100644
--- a/docs/installation/index.rst
+++ b/docs/installation/index.rst
@@ -1,5 +1,5 @@
 Installation
-=============
+============
 
 Requirements
 ------------
@@ -11,58 +11,9 @@ Regardless of your chosen installation method, the following requirements must b
 
 .. note::
 
-    Because of the federated nature of Funkwhale, **we strongly recommend you not to change the Funkwhale domain after initial deployment**, as it is likely to break
+    Because of the federated nature of Funkwhale, **it is strongly recommended not to change the Funkwhale domain after initial deployment**, as it is likely to break
     your installation.
 
-Available installation methods
--------------------------------
-
-Quick install
-^^^^^^^^^^^^^
-
-To install the latest version of Funkwhale on a recent Debian or Ubuntu server, run::
-
-    sudo apt-get update
-    sudo apt-get install curl
-    sudo sh -c "$(curl -sSL https://get.funkwhale.audio/)"
-
-This installation script will ask you a few questions, install the required dependencies
-and set up your instance.
-
-Additional info:
-
-- This script is based on our `Ansible role <https://dev.funkwhale.audio/funkwhale/ansible/>`_.
-- By default, the script installs Nginx, PostgreSQL, Redis and Funkwhale itself but you can customize the installation procedure if you already have some of these services available on your machine
-- Edit your pod configuration in ``/srv/funkwhale/ansible/playbook.yml`` and apply new configuration with ``sudo /srv/funkwhale/ansible/reconfigure``
-- Upgrade is done using ``sh -c "$(curl -sSL https://get.funkwhale.audio/upgrade.sh)"``.
-
-
-Alternative installation methods
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-We also offer Docker images, an installation guide for Debian 9 and Arch Linux, and `an
-Ansible role <https://dev.funkwhale.audio/funkwhale/ansible/>`_.
-
-.. toctree::
-   :maxdepth: 1
-
-   external_dependencies
-   debian
-   docker
-   systemd
-   non_amd64_architectures
-
-Third-party packages
-^^^^^^^^^^^^^^^^^^^^
-
-Funkwhale packages are available for the following platforms:
-
-- `YunoHost 3 <https://yunohost.org/>`_: https://github.com/YunoHost-Apps/funkwhale_ynh (kindly maintained by `@Jibec <https://github.com/Jibec>`_)
-- ArchLinux (as an AUR package): if you'd rather use a package, check out this alternative installation method on ArchLinux: https://wiki.archlinux.org/index.php/Funkwhale (package and wiki kindly maintained by getzee)
-- `NixOS <https://github.com/mmai/funkwhale-nixos>`_ (kindly maintained by @mmai)
-- `Helm chart <https://gitlab.com/ananace/charts/>`_ to install Funkwhale on Kubernetes (kindly maintained by `@ananace <https://gitlab.com/ananace>`_)
-- `HomelabOS <https://homelabos.com/docs/software/funkwhale/>`_
-
 Project architecture
 --------------------
 
@@ -80,28 +31,27 @@ The project relies on the following components and services to work:
     The synced clock is needed for federation purpose, to assess
     the validity of incoming requests.
 
-
 Hardware requirements
 ---------------------
 
 Funkwhale is not especially CPU hungry. On a dockerized instance with 2 CPUs
-and a few active users, the memory footprint is around ~500Mb::
+and a few active users, the memory footprint is around 500Mb::
 
    CONTAINER                   MEM USAGE
-   funkwhale_api_1             202.1 MiB
-   funkwhale_celerybeat_1      96.52 MiB
-   funkwhale_celeryworker_1    168.7 MiB
-   funkwhale_postgres_1        22.73 MiB
-   funkwhale_redis_1           1.496 MiB
+   funkwhale_api_1             202  MiB
+   funkwhale_celerybeat_1      96   MiB
+   funkwhale_celeryworker_1    168  MiB
+   funkwhale_postgres_1        22   MiB
+   funkwhale_redis_1           1    MiB
 
 Some users have reported running Funkwhale on Raspberry Pis with a memory
-consumption of less than 350MiB.
+consumption of less than 350 MiB.
 
 Thus, Funkwhale should run fine on commodity hardware, small hosting boxes and
 Raspberry Pi. We lack real-world examples of such deployments, so don't hesitate
 do give us your feedback (either positive or negative).
 
-Check out :doc:`optimization` for advice on how to tune your instance on small
+Check out :doc:`../admin/optimization` for advice on how to tune your instance on small
 configurations.
 
 Software requirements
@@ -119,6 +69,55 @@ dependencies should be available in your distribution's repositories.
    Funkwhale works only with Python >= 3.5, as we need support for async/await.
    Older versions of Python are not supported.
 
+Available installation methods
+-------------------------------
+
+Funkwhale can be installed using one of the following method:
+  
+- Quick install, the most straight forward way to get Funkwhale;
+- Mono-container Docker installation;
+- Multi-container Docker installation;
+- Manual Debian and Arch Linux installation;
+- `Ansible role <https://dev.funkwhale.audio/funkwhale/ansible/>`_.
+
+Further, Funkwhale packages are available for the following platforms:
+
+- `YunoHost 3 <https://yunohost.org/>`_: https://github.com/YunoHost-Apps/funkwhale_ynh (kindly maintained by `@Jibec <https://github.com/Jibec>`_)
+- ArchLinux (as an AUR package): if you'd rather use a package, check out this alternative installation method on ArchLinux: https://wiki.archlinux.org/index.php/Funkwhale (package and wiki kindly maintained by getzee)
+- `NixOS <https://github.com/mmai/funkwhale-nixos>`_ (kindly maintained by @mmai)
+- `Helm chart <https://gitlab.com/ananace/charts/>`_ to install Funkwhale on Kubernetes (kindly maintained by `@ananace <https://gitlab.com/ananace>`_)
+- `HomelabOS <https://homelabos.com/docs/software/funkwhale/>`_
+
+Quick install
+^^^^^^^^^^^^^
+
+To install the latest version of Funkwhale on a recent Debian or Ubuntu server, run::
+
+    sudo apt-get update
+    sudo apt-get install curl
+    sudo sh -c "$(curl -sSL https://get.funkwhale.audio/)"
+
+This installation script will ask you a few questions, install the required dependencies
+and set up your instance.
+
+Additional info:
+
+- This script is based on our `Ansible role <https://dev.funkwhale.audio/funkwhale/ansible/>`_.
+- By default, the script installs Nginx, PostgreSQL, Redis and Funkwhale itself but you can customize the installation procedure if you already have some of these services available on your machine
+- Edit your pod configuration in ``/srv/funkwhale/ansible/playbook.yml`` and apply new configuration with ``sudo /srv/funkwhale/ansible/reconfigure``
+- Upgrade is done using ``sh -c "$(curl -sSL https://get.funkwhale.audio/upgrade.sh)"``.
+
+
+Alternative installation methods
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. toctree::
+   :maxdepth: 1
+
+   debian
+   docker
+   non_amd64_architectures
+
 Running Funkwhale on the develop branch
 ---------------------------------------
 
@@ -144,18 +143,20 @@ since you may have to apply manual actions for your instance to continue to work
 
 .. _frontend-setup:
 
-Frontend setup
----------------
+Serving only the frontend
+-------------------------
 
 .. note::
 
     You do not need to do this if you are deploying using Docker, as frontend files
     are already included in the docker image.
 
+    You also do not need to do this if you are deploying manually on Debian or Arch, as this is covered by the corresponding documentation already.
+
 
 Files for the web frontend are purely static and can simply be downloaded, unzipped and served from any webserver:
 
-.. parsed-literal::
+.. code-block:: shell
 
     cd /srv/funkwhale
     curl -L -o front.zip "https://dev.funkwhale.audio/funkwhale/funkwhale/builds/artifacts/|version|/download?job=build_front"
@@ -163,24 +164,32 @@ Files for the web frontend are purely static and can simply be downloaded, unzip
 
 .. _reverse-proxy-setup:
 
-Reverse proxy
---------------
+Reverse proxy configuration
+---------------------------
 
 In order to make Funkwhale accessible from outside your server and to play nicely with other applications on your machine, you should configure a reverse proxy.
 
+We offer sample configurations for Nginx, Apache2 and Caddy.
+
+.. note::
+
+    You can freely adapt the proposed configuration to your own needs, as we cannot
+    cover every use case with a single template, especially when it's related
+    to SSL configuration.
+
 Nginx
 ^^^^^
 
 Ensure you have a recent version of nginx on your server. On Debian-like system, you would have to run the following:
 
-.. code-block:: bash
+.. code-block:: shell
 
     sudo apt-get update
     sudo apt-get install nginx
 
 On Arch Linux and its derivatives:
 
-.. code-block:: bash
+.. code-block:: shell
 
     sudo pacman -S nginx
 
@@ -188,11 +197,15 @@ To avoid configuration errors at this level, we will generate an nginx configura
 using your .env file. This will ensure your reverse-proxy configuration always
 match the application configuration and make upgrade/maintenance easier.
 
+.. note::
+    The following commands need to be run as superuser.
+
 On docker deployments, run the following commands:
 
-.. parsed-literal::
+.. code-block:: shell
 
     export FUNKWHALE_VERSION="|version|"
+
     # download the needed files
     curl -L -o /etc/nginx/funkwhale_proxy.conf "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale_proxy.conf"
     curl -L -o /etc/nginx/sites-available/funkwhale.template "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/docker.proxy.template"
@@ -209,7 +222,6 @@ On docker deployments, run the following commands:
 
 On non-docker deployments, run the following commands:
 
-
 .. parsed-literal::
 
     export FUNKWHALE_VERSION="|version|"
@@ -235,12 +247,6 @@ On non-docker deployments, run the following commands:
 
         grep '${' /etc/nginx/sites-available/funkwhale.conf
 
-.. note::
-
-    You can freely adapt the resulting file to your own needs, as we cannot
-    cover every use case with a single template, especially when it's related
-    to SSL configuration.
-
 Finally, enable the resulting configuration:
 
 .. code-block:: bash
@@ -249,38 +255,30 @@ Finally, enable the resulting configuration:
 
 .. warning::
 
-    If you plan to use to in-place import, ensure the alias value
+    If you plan to use in-place import, ensure the alias value
     in the ``_protected/music`` location matches your MUSIC_DIRECTORY_SERVE_PATH
     env var.
 
-HTTPS Configuration
-:::::::::::::::::::
-
-At this point you will need a SSL certificate to enable HTTPS on your server.
-The default nginx configuration assumes you have those available at ``/etc/letsencrypt/live/${FUNKWHALE_HOSTNAME}/``, which
-is the path used by `certbot <https://certbot.eff.org/docs/>`_ when generating certificates with Let's Encrypt.
-
-In you already have a certificate you'd like to use, simply update the nginx configuration
-and replace ``ssl_certificate`` and ``ssl_certificate_key`` values with the proper paths.
-
-If you don't have one, comment or remove the lines starting with ``ssl_certificate`` and ``ssl_certificate_key``. You can then proceed to generate
-a certificate, as shown below:
-
-.. code-block:: shell
-
-    # install certbot with nginx support
-    sudo apt install python-certbot-nginx
-    # generate the certificate
-    # (accept the terms of service if prompted)
-    sudo certbot --nginx -d yourfunkwhale.domain
+Finally, check that the configuration is valid with ``nginx -t`` then reload your nginx server with ``sudo systemctl reload nginx``.
 
-This should create a valid certificate and edit the nginx configuration to use the new certificate.
+.. note::
+    Music (and other static) files are never served by the app itself, but by the reverse
+    proxy. This is needed because a webserver is way more efficient at serving
+    files than a Python process.
 
-Reloading
-:::::::::
+    However, we do want to ensure users have the right to access music files, and
+    it can't be done at the proxy's level. To tackle this issue, `we use
+    nginx's internal directive <http://nginx.org/en/docs/http/ngx_http_core_module.html#internal>`_.
 
-Check the configuration is valid with ``nginx -t`` then reload your nginx server with ``sudo systemctl reload nginx``.
+    When the API receives a request on its music serving endpoint, it will check
+    that the user making the request can access the file. Then, it will return an empty
+    response with a ``X-Accel-Redirect`` header. This header will contain the path
+    to the file to serve to the user, and will be picked by nginx, but never sent
+    back to the client.
 
+    Using this technique, we can ensure music files are covered by the authentication
+    and permission policy of your instance, while remaining as performant
+    as possible.
 
 Apache2
 ^^^^^^^
@@ -312,11 +310,6 @@ You can tweak the configuration file according to your setup, especially the
 TLS configuration. Otherwise, defaults should work if you followed the
 installation guide.
 
-.. note::
-
-    To obtain a certificate to enable HTTPS on your server, please refer to the note in
-    the nginx chapter above.
-
 Check the configuration is valid with ``apache2ctl configtest``, and once you're
 done, load the new configuration with ``service apache2 restart``.
 
@@ -342,24 +335,36 @@ Caddy v1::
         }
     }
 
+HTTPS configuration
+^^^^^^^^^^^^^^^^^^^
+
+After configuring the reverse proxy, you need a SSL certificate to enable HTTPS on your server.
 
-About internal locations
-^^^^^^^^^^^^^^^^^^^^^^^^
+The default reverse proxy configuration assumes you have those available at ``/etc/letsencrypt/live/${FUNKWHALE_HOSTNAME}/``, which
+is the path used by `certbot <https://certbot.eff.org/docs/>`_ when generating certificates with Let's Encrypt.
+
+If you already have a certificate you would like to use, simply update the reverse proxy configuration
+and replace the following values with the proper paths:
+- For nginx: ``ssl_certificate`` and ``ssl_certificate_key``;
+- For Apache2: ``SSLCertificateFile`` and ``SSLCertificateKeyFile``.
+
+If you don't have one, comment or remove the lines starting with ``ssl_certificate`` and ``ssl_certificate_key`` for nginx, and ``SSLCertificateFile`` and ``SSLCertificateKeyFile`` for Apache2. You can then proceed to generate
+a certificate, as shown below. These instructions are provided by `certbot <https://certbot.eff.org/instructions>`:
+
+.. code-block:: shell
+
+    # install certbot
+    sudo snap install core; sudo snap refresh core
+    sudo snap install --classic certbot
+    sudo ln -s /snap/bin/certbot /usr/bin/certbot
+
+    # if you are using nginx: generate the certificate
+    sudo certbot --nginx -d yourfunkwhale.domain
 
-Music (and other static) files are never served by the app itself, but by the reverse
-proxy. This is needed because a webserver is way more efficient at serving
-files than a Python process.
+    # if you are using Apache2: generate the certificate
+    sudo certbot --apache -d yourfunkwhale.domain
 
-However, we do want to ensure users have the right to access music files, and
-it can't be done at the proxy's level. To tackle this issue, `we use
-nginx's internal directive <http://nginx.org/en/docs/http/ngx_http_core_module.html#internal>`_.
+This creates a valid certificate and edit the nginx or Apache2 configuration to use the new certificate. The certificate will be automatically renewed when they expire.
+    
 
-When the API receives a request on its music serving endpoint, it will check
-that the user making the request can access the file. Then, it will return an empty
-response with a ``X-Accel-Redirect`` header. This header will contain the path
-to the file to serve to the user, and will be picked by nginx, but never sent
-back to the client.
 
-Using this technique, we can ensure music files are covered by the authentication
-and permission policy of your instance, while remaining as performant
-as possible.
diff --git a/docs/installation/non_amd64_architectures.rst b/docs/installation/non_amd64_architectures.rst
index 57eaa072c0299fe2aa746db7f2034d92d5f68e86..9fe3ba9f745423b0eff1a39eacb34c38c811dcb7 100644
--- a/docs/installation/non_amd64_architectures.rst
+++ b/docs/installation/non_amd64_architectures.rst
@@ -42,7 +42,7 @@ guide.
 
 
 Building the mono-container Docker image (funkwhale/all-in-one)
---------------------------------------------------------------
+---------------------------------------------------------------
 
 This image is intended to be used in conjunction with our :ref:`Mono-container installation guide <docker-mono-container>`.
 guide.
diff --git a/docs/installation/systemd.rst b/docs/installation/systemd.rst
deleted file mode 100644
index 33f6a38ebe6eadfc53213bc62e81ea8476f420b2..0000000000000000000000000000000000000000
--- a/docs/installation/systemd.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-Systemd configuration
-----------------------
-
-.. note::
-
-    All the command lines below should be executed as root.
-
-Systemd offers a convenient way to manage your Funkwhale instance if you're
-not using docker.
-
-We'll see how to setup systemd to properly start a Funkwhale instance.
-
-First, download the sample unitfiles:
-
-.. parsed-literal::
-
-    curl -L -o "/etc/systemd/system/funkwhale.target" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale.target"
-    curl -L -o "/etc/systemd/system/funkwhale-server.service" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-server.service"
-    curl -L -o "/etc/systemd/system/funkwhale-worker.service" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-worker.service"
-    curl -L -o "/etc/systemd/system/funkwhale-beat.service" "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-beat.service"
-
-This will download three unitfiles:
-
-- ``funkwhale-server.service`` to launch the Funkwhale web server
-- ``funkwhale-worker.service`` to launch the Funkwhale task worker
-- ``funkwhale-beat.service`` to launch the Funkwhale task beat (this is for recurring tasks)
-- ``funkwhale.target`` to easily stop and start all of the services at once
-
-You can of course review and edit them to suit your deployment scenario
-if needed, but the defaults should be fine.
-
-Once the files are downloaded, reload systemd:
-
-.. code-block:: shell
-
-    systemctl daemon-reload
-
-And start the services:
-
-.. code-block:: shell
-
-    systemctl start funkwhale.target
-
-To ensure all Funkwhale processes are started automatically after a reboot, run:
-
-.. code-block:: shell
-    
-    systemctl enable funkwhale-server
-    systemctl enable funkwhale-worker
-    systemctl enable funkwhale-beat
-
-You can check the statuses of all processes like this:
-
-.. code-block:: shell
-
-    systemctl status funkwhale-\*
diff --git a/docs/moderator/reports.rst b/docs/moderator/reports.rst
index 746f42c1611be8eb561d8ebd42a1e063bb9ae3b0..e9a0f4f473ac5b2bbb4d3768e95f9682a36085c9 100644
--- a/docs/moderator/reports.rst
+++ b/docs/moderator/reports.rst
@@ -13,11 +13,11 @@ Clicking on this link will bring you to the list of unresolved reports. For conv
 the number of unresolved reports (if any) is also displayed directly next to this link, and updated in real time
 when new reports are submitted.
 
-Email notifications
+E-mail notifications
 -------------------
 
-In addition to the web UI, all moderators will receive a notification email whenever a report is 
-submitted or resolved providing your pod has a valid email sending configuration. 
+In addition to the web UI, all moderators will receive a notification e-mail whenever a report is 
+submitted or resolved providing your pod has a valid e-mail sending configuration. 
 This notification will include a link to review and handle the report, as well as additional 
 information about the report itself.
 
@@ -28,7 +28,7 @@ When viewing the moderation queue, you will be presented with the list of unreso
 
 Each report in the queue should include all the information you need to handle it, in particular:
 
-- Who submitted the report (or the email adress of the submitter if it's an accountless report)
+- Who submitted the report (or the e-mail adress of the submitter if it's an accountless report)
 - The report content
 - A link to the reported object, and a copy of this object data at the time the report was submitted
 
diff --git a/docs/swagger.yml b/docs/swagger.yml
index 904bd799eba6113597cd8d6d1bfeb65380d0af23..e52978bb75417e813b3fd3690bb381ce6d31dfed 100644
--- a/docs/swagger.yml
+++ b/docs/swagger.yml
@@ -285,7 +285,7 @@ paths:
     post:
       summary: Request a password reset
       description: |
-        Request a password reset. An email with reset instructions will be sent to the provided email,
+        Request a password reset. An e-mail with reset instructions will be sent to the provided e-mail address,
         if it's associated with a user account.
       tags:
         - "Auth and security"
@@ -341,7 +341,7 @@ paths:
                 $ref: "./api/definitions.yml#/Me"
   /api/v1/users/users/change-email/:
     post:
-      summary: Update the email address associated with a user account
+      summary: Update the e-mail address associated with a user account
       tags:
         - "Auth and security"
       requestBody:
diff --git a/docs/users/account.rst b/docs/users/account.rst
index 6d98919873165765c8c714e171fa7a4c3eb8182c..54dbfab3b4c3ccf2541dde3d11a4a9aea43ffd71 100644
--- a/docs/users/account.rst
+++ b/docs/users/account.rst
@@ -6,7 +6,7 @@ Delete your account
 
 You can delete your Funkwhale account by visiting your settings. The deletion form is found at the bottom of the page. You will need to input your password to confirm the deletion.
 
-Once the deletion request is submitted, your account and associated data will be removed from the server within a few minutes. This includes, but isn't limited to your avatar, email address, music, favorites, radios, followers and playlists.
+Once the deletion request is submitted, your account and associated data will be removed from the server within a few minutes. This includes, but isn't limited to your avatar, e-mail address, music, favorites, radios, followers and playlists.
 
 Your server will also broadcast a message to other server on the federation to inform them about the deletion.
 
diff --git a/docs/cli/index.rst b/docs/users/cli.rst
similarity index 84%
rename from docs/cli/index.rst
rename to docs/users/cli.rst
index baa3ad3374097ee89a3fc97210f606b09342fa4a..f7cf58736a74839556dc40c41cb2462befbe4665 100644
--- a/docs/cli/index.rst
+++ b/docs/users/cli.rst
@@ -16,8 +16,6 @@ Here is a (non-exhaustive) list of operations you can perform via the CLI:
 
 .. contents:: Table of Contents
 
-
-
 Installation
 ------------
 
@@ -54,13 +52,6 @@ Here are a couple of commands you can try to get started:
     # Search artists matching "zebra" on open.audio
     funkwhale -H https://open.audio artists ls "zebra"
 
-More examples
--------------
-
-You should find enough in this reference document to start using the CLI on your own.
-
-However, we've compiled :doc:`a list of example uses of the CLI <examples>` with advice and explanations, if you want to check it out ;)
-
 Getting help
 ------------
 
@@ -126,8 +117,8 @@ List of configuration options
 | ``-q``, ``--quiet``                  | ``FUNKWHALE_QUIET``                            | ``true``                                   | Completely disable logging                                    |
 +--------------------------------------+------------------------------------------------+--------------------------------------------+---------------------------------------------------------------+
 
-Listing results
----------------
+Read commands
+-------------
 
 All commands that list results - such as ``funkwhale albums ls`` or ``funkwhale tracks ls`` - share similar behaviors and sets of arguments.
 
@@ -238,8 +229,11 @@ The ``-i`` or ``--ids`` flag displays only the IDs of results, one per line::
 This is especially useful in conjunction with other commands (like deletion commands) and piping.
 Note that this is also technically equivalent to applying the ``--no-headers``, ``--format plain`` and ``--column ID`` flags.
 
+Write commands
+--------------
+
 Deleting objects
-----------------
+^^^^^^^^^^^^^^^^
 
 Some resources support deletion, via commands such as ``funkwhale libraries rm`` or ``funkwhale playlists rm``, followed by one or more IDs::
 
@@ -250,3 +244,68 @@ By default, the ``rm`` command will ask for confirmation, but you can disable th
 
 
 .. _API Documentation: https://docs.funkwhale.audio/swagger/
+
+Examples
+--------
+
+Uploading local files
+^^^^^^^^^^^^^^^^^^^^^
+
+**Goal**: create a library and upload all MP3 files from ``~/Music`` to it
+
+**Commands**::
+
+    funkwhale libraries create --name "My awesome library" --visibility me
+    # copy the returned UUID
+    funkwhale uploads create <UUID> ~/Music/**/*.mp3
+
+
+Favorite an entire album
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+**Goal**: retrieve all the tracks from an album and add these to your favorites
+
+**Commands**::
+
+    # retrieve the album ID
+    funkwhale albums ls "The Slip"
+
+    # Copy the ID, then retrieve 100 pages of tracks from that album
+    # get only the IDs and pipe those to the favorite creation command
+    funkwhale tracks ls -f "album=<ID>" --ids --limit 100 \
+        | xargs funkwhale favorites tracks create
+
+
+Mirror an artist discography locally
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+**Goal**: Download the discography of an artist locally, in the ``~/Music`` directory, in an ``Artist/Album/Track`` folder hierarchy
+
+**Commands**::
+
+    # retrieve the artist ID
+    funkwhale artists ls "Nine Inch Nails"
+
+    # Copy the ID, then retrieve 100 pages of tracks from that artist
+    # get only the IDs and pipe those to the download command
+    funkwhale tracks ls -f "artist=<ID>" --ids --limit 100 \
+        | xargs funkwhale tracks download \
+            -f mp3 -d ~/Music -t "{artist}/{album}/{title}.{extension}"
+
+
+Open a remote album in VLC
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+**Goal**: Variation of the previous example, but instead of downloading an artist discography, we listen to an album in VLC
+
+**Commands**::
+
+    # retrieve the album ID
+    funkwhale albums ls "The Slip"
+
+    # Copy the ID, then retrieve 100 pages of tracks from that album
+    # get only the IDs, download the corresponding tracks and pipe the audio
+    # directly to VLC
+    funkwhale tracks ls -f "album=<ID>" --ids --limit 100 \
+        | xargs funkwhale tracks download \
+        | vlc -
\ No newline at end of file
diff --git a/docs/users/create.rst b/docs/users/create.rst
index 0da1387986b265c243f09d198e25583ad7c97958..8c734f78cb6791a9eeda436707acaa4030290188 100644
--- a/docs/users/create.rst
+++ b/docs/users/create.rst
@@ -15,7 +15,7 @@ Signing up to an Instance
 Once you have chosen the instance you would like to sign up to, creating an account is easy.
 
 1. Click on the "Create an account" option on the left-hand side
-2. Enter your preferred username, email address, and password
+2. Enter your preferred username, e-mail address, and password
 3. [Optional] enter an Invitation code if you received an invite from an existing user
 4. Click on "Create my Account"
 5. Once you have created your account, you will be required to log in.
diff --git a/docs/users/index.rst b/docs/users/index.rst
index 6b4360d5438cf78ff640a15de1f169eab869459b..0cdcba34788077dcc0e0476fa151c71032cd5436 100644
--- a/docs/users/index.rst
+++ b/docs/users/index.rst
@@ -33,7 +33,7 @@ Using Funkwhale
    follow
    apps
    reports
-   ../cli/index
+   cli
 
 Troubleshooting Issues
 ----------------------
diff --git a/docs/users/reports.rst b/docs/users/reports.rst
index bf870e5c3a623d7165c51ac535ab7f4507caac21..802405171502d4b9b6f1e3d09798070d8596461b 100644
--- a/docs/users/reports.rst
+++ b/docs/users/reports.rst
@@ -29,7 +29,7 @@ Accountless reports
 If this feature is enabled on the pod you are browsing, you'll be able to submit reports without an account.
 
 This works exactly the same, but the report form will have an extra "email" field where you should include a working
-email address, in the event moderators need to contact you.
+e-mail address, in the event moderators need to contact you.
 
 Reporting an account
 --------------------
diff --git a/docs/users/upload.rst b/docs/users/upload.rst
index 872c2bcb7c3d7d6bfa38da215f1d3932c1e2e02d..6e67cde2fa7fd273b7cd06a36752155402fdafc0 100644
--- a/docs/users/upload.rst
+++ b/docs/users/upload.rst
@@ -3,11 +3,11 @@ Uploading Content To Funkwhale
 
 To upload content to any Funkwhale instance, you need:
 
-1. :doc:`An account on that instance <create>`
-2. :ref:`Storage space <upload-storage>`
-3. :ref:`A library <upload-library>` or :ref:`channel <upload-channel>`
-4. :ref:`Properly tagged files <upload-tagging>`
-5. :ref:`To upload your files <upload-upload>`
+1. :doc:`An account on that instance <create>`;
+2. :ref:`Storage space <upload-storage>`;
+3. :ref:`A library <upload-library>` or :ref:`channel <upload-channel>`;
+4. :ref:`Properly tagged files <upload-tagging>`;
+5. To upload your files :ref:`to a library <upload_library>` or :ref:`to a channel <channel_upload>`.
 
 .. _upload-storage:
 
@@ -93,14 +93,14 @@ However, Funkwhale can understand and use additional tags to enhance user experi
 | ``Artist`` (required)            | ``Nine Inch Nails``                        | The artist name                                               |
 |                                  |                                            |                                                               |
 +----------------------------------+--------------------------------------------+---------------------------------------------------------------+
-| ``Album``                        | ``The Slip``                               | The album title. If none is provided, an [Unknown Album]     |
+| ``Album``                        | ``The Slip``                               | The album title. If none is provided, an [Unknown Album]      |
 |                                  |                                            | entry will be created                                         |
 +----------------------------------+--------------------------------------------+---------------------------------------------------------------+
 | ``Album artist``                 | ``Trent Reznor``                           | The album artist name (can be different than the track        |
 |                                  |                                            | artist)                                                       |
 |                                  |                                            |                                                               |
 +----------------------------------+--------------------------------------------+---------------------------------------------------------------+
-| ``Genre``                        | ``Industrial, Metal``                      | A comma separated list of tags to associate with the track     |
+| ``Genre``                        | ``Industrial, Metal``                      | A comma separated list of tags to associate with the track    |
 |                                  |                                            | Other supported separators: ``;`` and ``/``                   |
 +----------------------------------+--------------------------------------------+---------------------------------------------------------------+
 | ``Track number``                 | ``4``                                      | The position of the track in the album/release                |
diff --git a/front/src/App.vue b/front/src/App.vue
index 85c9391a5128ed169b14038be67d75d8630295ea..5ac09cb8ea9997bed9d7ac906a20a73447862458 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -67,6 +67,7 @@ export default {
       showShortcutsModal: false,
       showSetInstanceModal: false,
       initialTitle: document.title,
+      width: window.innerWidth
     }
   },
   async created () {
@@ -82,7 +83,8 @@ export default {
         }
       );
     }
-
+    window.addEventListener('resize', this.handleResize);
+    this.handleResize();
     this.openWebsocket()
     let self = this
     if (!this.$store.state.ui.selectedLanguage) {
@@ -293,6 +295,9 @@ export default {
       this.$store.commit('ui/serviceWorker', {updateAvailable: false})
       if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return; }
       this.serviceWorker.registration.waiting.postMessage({command: 'skipWaiting'})
+    },
+    handleResize() {
+      this.width = window.innerWidth
     }
   },
   computed: {
diff --git a/front/src/components/About.vue b/front/src/components/About.vue
index d155caa59ae8dda41f2453bfa0b7f2bcabcb40f1..a9ef8811144c90a8354410a07204244a7bda015d 100644
--- a/front/src/components/About.vue
+++ b/front/src/components/About.vue
@@ -7,7 +7,7 @@
             v-translate="{podName: podName}"
             translate-context="Content/Home/Header"
             :translate-params="{podName: podName}">
-            About %{ podName }!
+            About %{ podName }
           </span>
           <div v-if="shortDescription" class="sub header">
             {{ shortDescription }}
@@ -179,16 +179,16 @@
                   <i class="music really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}" :translate-n="parseInt(stats.hours)" translate-plural="%{ count } hours of music">%{ count } hour of music</translate>
                 </p>
                 <p v-if="stats.artists">
-                  <i class="users really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.artists.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.artists" translate-plural="%{ count } artists">%{ count } artists</translate>
+                  <i class="users really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.artists.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.artists" translate-plural="%{ count } artists">%{ count } artist</translate>
                 </p>
                 <p v-if="stats.albums">
-                  <i class="headphones really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.albums.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.albums" translate-plural="%{ count } albums">%{ count } albums</translate>
+                  <i class="headphones really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.albums.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.albums" translate-plural="%{ count } albums">%{ count } album</translate>
                 </p>
                 <p v-if="stats.tracks">
-                  <i class="file really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.tracks.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.tracks" translate-plural="%{ count } tracks">%{ count } tracks</translate>
+                  <i class="file really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.tracks.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.tracks" translate-plural="%{ count } tracks">%{ count } track</translate>
                 </p>
                 <p v-if="stats.listenings">
-                  <i class="play really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.listenings.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.listenings" translate-plural="%{ count } listenings">%{ count } listenings</translate>
+                  <i class="play really discrete icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.listenings.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.listenings" translate-plural="%{ count } listenings">%{ count } listening</translate>
                 </p>
               </template>
             </div>
diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue
index 794ffaa48699f85aed2ebc168f2ddcdd0b85c6b5..c5a7d4a5fc96744a7b65ee427ba2f64bc2815dc4 100644
--- a/front/src/components/Footer.vue
+++ b/front/src/components/Footer.vue
@@ -14,7 +14,7 @@
           </h4>
           <div class="ui list">
             <router-link v-if="this.$route.path != '/about'" class="link item" to="/about">
-              <translate translate-context="Footer/About/List item.Link">About page</translate>
+              <translate translate-context="Footer/About/List item.Link">About</translate>
             </router-link>
             <router-link v-else-if="this.$route.path == '/about' && $store.state.auth.authenticated" class="link item" to="/library">
               <translate translate-context="Footer/*/List item.Link">Go to Library</translate>
@@ -71,7 +71,7 @@
           </div>
           <div class="ui hidden divider"></div>
           <p>
-            <translate translate-context="Footer/*/List item.Link">The funkwhale logo was kindly designed and provided by Francis Gading.</translate>
+            <translate translate-context="Footer/*/List item.Link">The Funkwhale logo was kindly designed and provided by Francis Gading.</translate>
           </p>
         </section>
       </div>
diff --git a/front/src/components/Home.vue b/front/src/components/Home.vue
index 5a59ed0f7c304daf142601519c50220afdc6621c..92615c985b6fdeab5d1284c9681406a902f46de5 100644
--- a/front/src/components/Home.vue
+++ b/front/src/components/Home.vue
@@ -107,7 +107,7 @@
           </h3>
           <template v-if="openRegistrations">
             <p>
-              <translate translate-context="Content/Home/Paragraph">Sign up now to keep a track of your favorites, create playlists, discover new content and much more!</translate>
+              <translate translate-context="Content/Home/Paragraph">Sign up now to keep track of your favorites, create playlists, discover new content and much more!</translate>
             </p>
             <p v-if="defaultUploadQuota">
               <translate translate-context="Content/Home/Paragraph" :translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}">Users on this pod also get %{ quota } of free storage to upload their own content!</translate>
diff --git a/front/src/components/audio/ChannelForm.vue b/front/src/components/audio/ChannelForm.vue
index 5e58c885a0a1a4dd1127b334ba057ca32f457aac..fbdb8280a87d1ddd18afd7a07a6eb3daeda8e9ff 100644
--- a/front/src/components/audio/ChannelForm.vue
+++ b/front/src/components/audio/ChannelForm.vue
@@ -9,7 +9,7 @@
     <template v-if="metadataChoices">
       <fieldset v-if="creating && step === 1" class="ui grouped channel-type required field">
         <legend>
-          <translate translate-context="Content/Channel/Paragraph">What this channel will be used for?</translate>
+          <translate translate-context="Content/Channel/Paragraph">What will this channel be used for?</translate>
         </legend>
         <div class="ui hidden divider"></div>
         <div class="field">
@@ -33,7 +33,7 @@
         </div>
         <div class="ui required field">
           <label for="channel-username">
-            <translate translate-context="Content/Channel/*">Social Network Name</translate>
+            <translate translate-context="Content/Channel/*">Fediverse handle</translate>
           </label>
           <div class="ui left labeled input">
             <div class="ui basic label">@</div>
@@ -42,7 +42,7 @@
           <template v-if="creating">
             <div class="ui small hidden divider"></div>
             <p>
-              <translate translate-context="Content/Channels/Paragraph">Used in URLs and to follow this channel on the federation. You cannot change it afterwards.</translate>
+              <translate translate-context="Content/Channels/Paragraph">Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.</translate>
             </p>
           </template>
         </div>
@@ -123,7 +123,7 @@
         <div class="ui two fields" v-if="newValues.content_category === 'podcast'">
           <div class="ui field">
             <label for="channel-itunes-email">
-              <translate translate-context="*/*/*">Owner email</translate>
+              <translate translate-context="*/*/*">Owner e-mail address</translate>
             </label>
             <input
               name="channel-itunes-email"
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index bb91533f48f18cc2ea3d5838af1e9a5519787687..6e1a61173b31ded0d7ccb9b0767968f2d26c4e51 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -697,8 +697,8 @@ export default {
     labels() {
       let audioPlayer = this.$pgettext('Sidebar/Player/Hidden text', "Media player")
       let previous = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Previous track")
-      let play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Play track")
-      let pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Pause track")
+      let play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Play")
+      let pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Pause")
       let next = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Next track")
       let unmute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Unmute")
       let mute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Mute")
diff --git a/front/src/components/auth/LoginForm.vue b/front/src/components/auth/LoginForm.vue
index 6d19b81cf5b0a0953ee4393bed46c5db59f5ffa9..b7515c4277fbd2f752b65e7bfeeb9a41c1e8c532 100644
--- a/front/src/components/auth/LoginForm.vue
+++ b/front/src/components/auth/LoginForm.vue
@@ -4,10 +4,10 @@
       <h4 class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></h4>
       <ul class="list">
         <li v-if="error == 'invalid_credentials' && $store.state.instance.settings.moderation.signup_approval_enabled.value">
-          <translate translate-context="Content/Login/Error message.List item/Call to action">If you signed-up recently, you may need to wait before our moderation team review your account, or verify your email.</translate>
+          <translate translate-context="Content/Login/Error message.List item/Call to action">If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.</translate>
         </li>
         <li v-else-if="error == 'invalid_credentials'">
-          <translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct and ensure you verified your email.</translate>
+          <translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check that your username and password combination is correct and make sure you verified your e-mail address.</translate>
         </li>
         <li v-else>{{ error }}</li>
       </ul>
@@ -15,7 +15,7 @@
     <template v-if="$store.getters['instance/appDomain'] === $store.getters['instance/domain']" >
       <div class="field">
         <label for="username-field">
-          <translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate>
+          <translate translate-context="Content/Login/Input.Label/Noun">Username or e-mail address</translate>
           <template v-if="showSignup">
             |
             <router-link :to="{path: '/signup'}">
@@ -92,7 +92,7 @@ export default {
   },
   computed: {
     labels() {
-      let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email")
+      let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or e-mail address")
       return {
         usernamePlaceholder,
       }
diff --git a/front/src/components/auth/Settings.vue b/front/src/components/auth/Settings.vue
index 94f033dfe20c301d7bda035694a72e7a5452f582..0bda5b2ab3b8c70d2e7144b6d0834f372cb542e9 100644
--- a/front/src/components/auth/Settings.vue
+++ b/front/src/components/auth/Settings.vue
@@ -72,7 +72,7 @@
             </ul>
           </div>
           <div class="field">
-            <label for="old-password-field"><translate translate-context="Content/Settings/Input.Label">Old password</translate></label>
+            <label for="old-password-field"><translate translate-context="Content/Settings/Input.Label">Current password</translate></label>
             <password-input field-id="old-password-field" required v-model="old_password" />
           </div>
           <div class="field">
@@ -205,9 +205,9 @@
             <translate translate-context="Content/Settings/Title/Noun">Your applications</translate>
           </div>
         </h2>
-        <p><translate translate-context="Content/Settings/Paragraph">This is the list of applications that you have created.</translate></p>
+        <p><translate translate-context="Content/Settings/Paragraph">This is the list of applications that you have registered.</translate></p>
         <router-link class="ui success button" :to="{name: 'settings.applications.new'}">
-          <translate translate-context="Content/Settings/Button.Label">Create a new application</translate>
+          <translate translate-context="Content/Settings/Button.Label">Register a new application</translate>
         </router-link>
         <table v-if="ownedApps.length > 0" class="ui compact very basic unstackable table">
           <thead>
@@ -238,10 +238,10 @@
                 <dangerous-button
                   class="ui tiny danger button"
                   @confirm="deleteApp(app.client_id)">
-                  <translate translate-context="*/*/*/Verb">Delete</translate>
-                  <p slot="modal-header" v-translate="{application: app.name}" translate-context="Popup/Settings/Title">Delete application "%{ application }"?</p>
-                  <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will permanently delete the application and all the associated tokens.</translate></p>
-                  <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Delete application</translate></div>
+                  <translate translate-context="*/*/*/Verb">Remove</translate>
+                  <p slot="modal-header" v-translate="{application: app.name}" translate-context="Popup/Settings/Title">Remove application "%{ application }"?</p>
+                  <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will permanently remove the application and all the associated tokens.</translate></p>
+                  <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Remove application</translate></div>
                 </dangerous-button>
               </td>
             </tr>
@@ -249,10 +249,10 @@
         </table>
         <empty-state v-else>
           <translate slot="title" translate-context="Content/Applications/Paragraph">
-            You don't have any configured application yet.
+            You don't have registered any application yet.
           </translate>
           <translate translate-context="Content/Applications/Paragraph">
-            Create one to integrate Funkwhale with third-party applications.
+            Register one to integrate Funkwhale with third-party applications.
           </translate>
         </empty-state>
       </section>
@@ -275,24 +275,24 @@
         <h2 class="ui header">
           <i class="comment icon"></i>
           <div class="content">
-            <translate translate-context="*/*/Button.Label">Change my email address</translate>
+            <translate translate-context="*/*/Button.Label">Change my e-mail address</translate>
           </div>
         </h2>
         <p>
-          <translate translate-context="Content/Settings/Paragraph'">Change the email address associated with your account. We will send a confirmation to the new address.</translate>
+          <translate translate-context="Content/Settings/Paragraph'">Change the e-mail address associated with your account. We will send a confirmation to the new address.</translate>
         </p>
         <p>
-          <translate :translate-params="{email: $store.state.auth.profile.email}" translate-context="Content/Settings/Paragraph'">Your current email address is %{ email }.</translate>
+          <translate :translate-params="{email: $store.state.auth.profile.email}" translate-context="Content/Settings/Paragraph'">Your current e-mail address is %{ email }.</translate>
         </p>
         <form class="ui form" @submit.prevent="changeEmail">
           <div v-if="changeEmailErrors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Settings/Error message.Title">We cannot change your email address</translate></h4>
+            <h4 class="header"><translate translate-context="Content/Settings/Error message.Title">We cannot change your e-mail address</translate></h4>
             <ul class="list">
               <li v-for="error in changeEmailErrors">{{ error }}</li>
             </ul>
           </div>
           <div class="field">
-            <label for="new-email"><translate translate-context="*/*/*">New email</translate></label>
+            <label for="new-email"><translate translate-context="*/*/*">New e-mail address</translate></label>
             <input id="new-email" required v-model="newEmail" type="email" />
           </div>
           <div class="field">
@@ -570,7 +570,7 @@ export default {
             self.isChangingEmail = false
             self.newEmail = null
             self.emailPassword = null
-            let msg = self.$pgettext('*/Auth/Message', 'Your email has been changed, please check your inbox for our confirmation message.')
+            let msg = self.$pgettext('*/Auth/Message', 'Your e-mail address has been changed, please check your inbox for our confirmation message.')
             self.$store.commit('ui/addMessage', {
               content: msg,
               date: new Date()
diff --git a/front/src/components/auth/SignupForm.vue b/front/src/components/auth/SignupForm.vue
index d037da3c343fdce882a01aa07bcda203a75822ed..35bb8df7b172457a857eee57d69b3558250917b4 100644
--- a/front/src/components/auth/SignupForm.vue
+++ b/front/src/components/auth/SignupForm.vue
@@ -2,10 +2,10 @@
   <div v-if="submitted">
     <div class="ui success message">
       <p v-if="signupRequiresApproval">
-        <translate translate-context="Content/Signup/Form/Paragraph">Your account request was successfully submitted. You will be notified by email when our moderation team has reviewed your request.</translate>
+        <translate translate-context="Content/Signup/Form/Paragraph">Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.</translate>
       </p>
       <p v-else>
-        <translate translate-context="Content/Signup/Form/Paragraph">Your account was successfully created. Please verify your email before trying to login.</translate>
+        <translate translate-context="Content/Signup/Form/Paragraph">Your account was successfully created. Please verify your e-mail address before trying to login.</translate>
       </p>
     </div>
     <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
@@ -44,7 +44,7 @@
       v-model="username">
     </div>
     <div class="required field">
-      <label for="email-field"><translate translate-context="Content/*/*/Noun">Email</translate></label>
+      <label for="email-field"><translate translate-context="Content/*/*/Noun">E-mail address</translate></label>
       <input
       id="email-field"
       ref="email"
@@ -135,7 +135,7 @@ export default {
         "Enter your invitation code (case insensitive)"
       )
       let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username")
-      let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email")
+      let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your e-mail address")
       return {
         usernamePlaceholder,
         emailPlaceholder,
diff --git a/front/src/components/auth/SubsonicTokenForm.vue b/front/src/components/auth/SubsonicTokenForm.vue
index fa11dd911f9dc8c395d809bc92e7e1d17b89b3de..4659bfac770842a5a1a0fa0e7641b8faec04b4bc 100644
--- a/front/src/components/auth/SubsonicTokenForm.vue
+++ b/front/src/components/auth/SubsonicTokenForm.vue
@@ -8,7 +8,7 @@
       <translate translate-context="Content/Settings/Paragraph'">Funkwhale is compatible with other music players that support the Subsonic API.</translate>&nbsp;<translate translate-context="Content/Settings/Paragraph">You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</translate>
     </p>
     <p>
-      <translate translate-context="Content/Settings/Paragraph">However, accessing Funkwhale from those clients require a separate password you can set below.</translate>
+      <translate translate-context="Content/Settings/Paragraph">However, accessing Funkwhale from those clients requires a separate password you can set below.</translate>
     </p>
     <p><a href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients" target="_blank">
       <translate translate-context="Content/Settings/Link">Discover how to use Funkwhale from other apps</translate>
diff --git a/front/src/components/channels/AlbumModal.vue b/front/src/components/channels/AlbumModal.vue
index 2346c14cbe5bce9df65ea35e8434c3a3c29aacc9..5dae29156c673b8acbc02a123902d3e5213db8fb 100644
--- a/front/src/components/channels/AlbumModal.vue
+++ b/front/src/components/channels/AlbumModal.vue
@@ -1,7 +1,7 @@
 <template>
   <modal class="small" :show.sync="show">
     <h4 class="header">
-      <translate key="1" v-if="channel.content_category === 'podcasts'" translate-context="Popup/Channels/Title/Verb">New serie</translate>
+      <translate key="1" v-if="channel.content_category === 'podcasts'" translate-context="Popup/Channels/Title/Verb">New series</translate>
       <translate key="2" v-else translate-context="Popup/Channels/Title">New album</translate>
     </h4>
     <div class="scrolling content">
diff --git a/front/src/components/channels/AlbumSelect.vue b/front/src/components/channels/AlbumSelect.vue
index 0beafbbc721427130c5a499ba2be909d0ba6ff75..7b37704693f0e26fac21d54b09ac9edaad3c59c7 100644
--- a/front/src/components/channels/AlbumSelect.vue
+++ b/front/src/components/channels/AlbumSelect.vue
@@ -1,7 +1,7 @@
 <template>
   <div>
     <label for="album-dropdown">
-      <translate v-if="channel && channel.artist.content_category === 'podcast'" key="1" translate-context="*/*/*">Serie</translate>
+      <translate v-if="channel && channel.artist.content_category === 'podcast'" key="1" translate-context="*/*/*">Series</translate>
       <translate v-else key="2" translate-context="*/*/*">Album</translate>
     </label>
     <select id="album-dropdown" :value="value" @input="$emit('input', $event.target.value)" class="ui search normal dropdown">
diff --git a/front/src/components/common/ActionTable.vue b/front/src/components/common/ActionTable.vue
index 38a6d23d02b39b0e023f25d5be8f265b2dd3164d..0fe89a2005233b98cedd510060813c9fcc817b5e 100644
--- a/front/src/components/common/ActionTable.vue
+++ b/front/src/components/common/ActionTable.vue
@@ -82,7 +82,7 @@
                         :translate-n="objectsData.count"
                         :translate-params="{total: objectsData.count}"
                         translate-plural="Select all %{ total } elements">
-                        Select all %{ total } elements
+                        Select one element
                       </translate>
                     </a>
                     <a @click.prevent="selectAll = false" v-else href="">
diff --git a/front/src/components/federation/FetchButton.vue b/front/src/components/federation/FetchButton.vue
index c19756fb7094467d1e2405c882e0c245d14141bb..65ff28a09c3a192f66e2948635eb26978d53b43a 100644
--- a/front/src/components/federation/FetchButton.vue
+++ b/front/src/components/federation/FetchButton.vue
@@ -5,7 +5,7 @@
     </div>
     <modal class="small" :show.sync="showModal">
       <h3 class="header">
-        <translate translate-context="Popup/*/Title">Refreshing object from remote…</translate>
+        <translate translate-context="Popup/*/Title">Refreshing object from remote server…</translate>
       </h3>
       <div class="scrolling content">
         <template v-if="fetch && fetch.status != 'pending'">
@@ -78,7 +78,7 @@
         </div>
         <div v-else-if="fetch && fetch.status === 'pending' && pollsCount >= maxPolls" role="alert" class="ui warning message">
           <h4 class="header"><translate translate-context="Popup/*/Message.Title">Refresh pending</translate></h4>
-          <p><translate translate-context="Popup/*/Message.Content">Refresh request wasn't proceed in time by our server. It will be processed later.</translate></p>
+          <p><translate translate-context="Popup/*/Message.Content">The refresh request hasn't been processed in time by our server. It will be processed later.</translate></p>
         </div>
       </div>
       <div class="actions">
diff --git a/front/src/components/library/Podcasts.vue b/front/src/components/library/Podcasts.vue
index e4683407a83d87faf51acf273e55321f02daa499..75607ab08bfae2c5b37fbee702918056df26c858 100644
--- a/front/src/components/library/Podcasts.vue
+++ b/front/src/components/library/Podcasts.vue
@@ -2,13 +2,13 @@
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
       <h2 class="ui header">
-        <translate translate-context="Content/Podcasts/Title">Browsing Podcasts</translate>
+        <translate translate-context="Content/Podcasts/Title">Browsing podcasts</translate>
       </h2>
       <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updatePage();updateQueryString();fetchData()">
         <div class="fields">
           <div class="field">
             <label for="artist-search">
-              <translate translate-context="Content/Search/Input.Label/Noun">Podcast Title</translate>
+              <translate translate-context="Content/Search/Input.Label/Noun">Podcast title</translate>
             </label>
             <div class="ui action input">
               <input id="artist-search" type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
diff --git a/front/src/components/library/Radios.vue b/front/src/components/library/Radios.vue
index 8fd6142ac1f58c73939c9074b644982ea9405a99..8f2b551ae2b6059c74d79561dae57d102fd792d3 100644
--- a/front/src/components/library/Radios.vue
+++ b/front/src/components/library/Radios.vue
@@ -13,6 +13,7 @@
           <radio-card v-if="isAuthenticated" :type="'actor-content'" :object-id="$store.state.auth.fullUsername"></radio-card>
           <radio-card v-if="isAuthenticated && hasFavorites" :type="'favorites'"></radio-card>
           <radio-card :type="'random'"></radio-card>
+          <radio-card :type="'recently-added'"></radio-card>
           <radio-card v-if="$store.state.auth.authenticated" :type="'less-listened'"></radio-card>
         </div>
       </div>
diff --git a/front/src/components/library/TagsSelector.vue b/front/src/components/library/TagsSelector.vue
index 2084daceb5456fe4a07996e33f6086280558a37f..c19a5ece48521faa6d18749b3c9b1bee95a17f7c 100644
--- a/front/src/components/library/TagsSelector.vue
+++ b/front/src/components/library/TagsSelector.vue
@@ -52,7 +52,16 @@ export default {
               ...response,
             }
             if (currentSearch) {
-              response.results = [{name: currentSearch}, ...response.results]
+              let existingTag = response.results.find((result) => result.name === currentSearch)
+              if (existingTag) {
+                if (response.results.indexOf(existingTag) !== 0) {
+                  response.results = [existingTag, ...response.results]
+                  response.results.splice(response.results.indexOf(existingTag) + 1, 1)
+                }
+              }
+              else {
+                response.results = [{name: currentSearch}, ...response.results]
+              }
             }
             return response
           }
diff --git a/front/src/components/manage/moderation/InstancePolicyForm.vue b/front/src/components/manage/moderation/InstancePolicyForm.vue
index 2d633176c62739c2935ef3d1b74bfdbfc8ce5085..5fc072165cd6c9352cf0af7755c4593d34a83d52 100644
--- a/front/src/components/manage/moderation/InstancePolicyForm.vue
+++ b/front/src/components/manage/moderation/InstancePolicyForm.vue
@@ -107,7 +107,7 @@ export default {
   computed: {
     labels () {
       return {
-        summaryHelp: this.$pgettext('Content/Moderation/Help text', "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place."),
+        summaryHelp: this.$pgettext('Content/Moderation/Help text', "Explain why you're applying this policy: this will help you remember why you added this rule. Depending on your pod configuration, this may be displayed publicly to help users understand the moderation rules in place."),
         isActiveHelp: this.$pgettext('Content/Moderation/Help text', "Use this setting to temporarily enable/disable the policy without completely removing it."),
         blockAllHelp: this.$pgettext('Content/Moderation/Help text', "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)"),
         silenceActivity: {
diff --git a/front/src/components/manage/users/InvitationsTable.vue b/front/src/components/manage/users/InvitationsTable.vue
index ca72e4a5c321efe92141d1bf297854a63ed9a314..62e5a4f05c6f7770c02a0f369f740868eea3a75b 100644
--- a/front/src/components/manage/users/InvitationsTable.vue
+++ b/front/src/components/manage/users/InvitationsTable.vue
@@ -75,8 +75,10 @@
 
       <span v-if="result && result.results.length > 0">
         <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
-          Showing results %{ start }-%{ end } on %{ total }
+          translate-plural="Showing results %{ start } to %{ end } from %{ total }"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+          :translate-n="result.count">
+          Showing one result
         </translate>
       </span>
     </div>
diff --git a/front/src/components/manage/users/UsersTable.vue b/front/src/components/manage/users/UsersTable.vue
index 910cff0eaec17d65ce46046938b13b58ac1c77ee..3ad6210ca7a6c6c2f1ad6307094c4ffed112639e 100644
--- a/front/src/components/manage/users/UsersTable.vue
+++ b/front/src/components/manage/users/UsersTable.vue
@@ -87,8 +87,10 @@
 
       <span v-if="result && result.results.length > 0">
         <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
-          Showing results %{ start }-%{ end } on %{ total }
+          translate-plural="Showing results %{ start } to %{ end } from %{ total }"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+          :translate-n="result.count">
+          Showing one result
         </translate>
       </span>
     </div>
diff --git a/front/src/components/mixins/Translations.vue b/front/src/components/mixins/Translations.vue
index 138922f077390a870e0eead57d5b7d97283786b4..2d2631e8748d2bab5f40c9faad8164b4f693c18b 100644
--- a/front/src/components/mixins/Translations.vue
+++ b/front/src/components/mixins/Translations.vue
@@ -77,7 +77,7 @@ export default {
           album_title: this.$pgettext('Content/*/Dropdown/Noun', 'Album name'),
           artist_name: this.$pgettext('Content/*/Dropdown/Noun', 'Artist name'),
           name: this.$pgettext('*/*/*/Noun', 'Name'),
-          length: this.$pgettext('*/*/*/Noun', 'Length'),
+          length: this.$pgettext('*/*/*/Noun', 'Duration'),
           items_count: this.$pgettext('*/*/*/Noun', 'Items'),
           size: this.$pgettext('Content/*/*/Noun', 'Size'),
           bitrate: this.$pgettext('Content/Track/*/Noun', 'Bitrate'),
@@ -94,7 +94,7 @@ export default {
         scopes: {
           profile: {
             label: this.$pgettext('Content/OAuth Scopes/Label', 'Profile'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to email, username, and profile information'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to e-mail, username, and profile information'),
           },
           libraries: {
             label: this.$pgettext('Content/OAuth Scopes/Label', 'Libraries and uploads'),
diff --git a/front/src/components/moderation/ReportModal.vue b/front/src/components/moderation/ReportModal.vue
index f32f7b6f6f0aa52c3e7527372524be1654d9a5de..bc308f5e251065776e0b31d8604d0304a2108080 100644
--- a/front/src/components/moderation/ReportModal.vue
+++ b/front/src/components/moderation/ReportModal.vue
@@ -33,7 +33,7 @@
             </label>
             <input type="email" v-model="submitterEmail" name="report-submitter-email" id="report-submitter-email" required>
             <p>
-              <translate translate-context="*/*/Field,Help">We'll use this email if we need to contact you regarding this report.</translate>
+              <translate translate-context="*/*/Field,Help">We'll use this e-mail address if we need to contact you regarding this report.</translate>
             </p>
           </div>
         </div>
diff --git a/front/src/components/playlists/Editor.vue b/front/src/components/playlists/Editor.vue
index 43d080dfa8a28b9ce29ddbee5017f44001682615..a088e17372fe1178f6fdf83e1db778ed994d9daf 100644
--- a/front/src/components/playlists/Editor.vue
+++ b/front/src/components/playlists/Editor.vue
@@ -192,7 +192,7 @@ export default {
     }),
     labels () {
       return {
-        copyTitle: this.$pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy queued tracks to playlist')
+        copyTitle: this.$pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy the current queue to this playlist')
       }
     },
     status () {
diff --git a/front/src/store/radios.js b/front/src/store/radios.js
index 9cd48e769ded3b3f4746a462f4d5a975e0019833..475b2457f21bd5494e46df6ec6beb701eb21c644 100644
--- a/front/src/store/radios.js
+++ b/front/src/store/radios.js
@@ -27,6 +27,10 @@ export default {
         'less-listened': {
           name: 'Less listened',
           description: "Listen to tracks you usually don't. It's time to restore some balance."
+        },
+        'recently-added': {
+          name: 'Recently Added',
+          description: "Newest content on the network. Get some fresh air."
         }
       }
     }
diff --git a/front/src/views/auth/PasswordReset.vue b/front/src/views/auth/PasswordReset.vue
index 583ff2e2a45042a0b98755be23a7b104496fc5ec..380e95a8f7c30dcd5abbb7515275ac7eca91b6c6 100644
--- a/front/src/views/auth/PasswordReset.vue
+++ b/front/src/views/auth/PasswordReset.vue
@@ -10,9 +10,9 @@
               <li v-for="error in errors">{{ error }}</li>
             </ul>
           </div>
-          <p><translate translate-context="Content/Signup/Paragraph">Use this form to request a password reset. We will send an email to the given address with instructions to reset your password.</translate></p>
+          <p><translate translate-context="Content/Signup/Paragraph">Use this form to request a password reset. We will send an e-mail to the given address with instructions to reset your password.</translate></p>
           <div class="field">
-            <label for="account-email"><translate translate-context="Content/Signup/Input.Label">Account's email</translate></label>
+            <label for="account-email"><translate translate-context="Content/Signup/Input.Label">Account's e-mail address</translate></label>
             <input
               id="account-email"
               required
@@ -52,7 +52,7 @@ export default {
   computed: {
     labels() {
       let reset = this.$pgettext('*/Login/*/Verb', "Reset your password")
-      let placeholder = this.$pgettext('Content/Signup/Input.Placeholder', "Enter the email address linked to your account"
+      let placeholder = this.$pgettext('Content/Signup/Input.Placeholder', "Enter the e-mail address linked to your account"
       )
       return {
         reset,
diff --git a/front/src/views/auth/PasswordResetConfirm.vue b/front/src/views/auth/PasswordResetConfirm.vue
index a10d664f801291c2aca1b54c7ff374978c3b9572..7d0f4b84fdd7c4288eeb421a2d2942b0599229cd 100644
--- a/front/src/views/auth/PasswordResetConfirm.vue
+++ b/front/src/views/auth/PasswordResetConfirm.vue
@@ -22,7 +22,7 @@
               <translate translate-context="Content/Signup/Button.Label">Update your password</translate></button>
           </template>
           <template v-else>
-            <p><translate translate-context="Content/Signup/Paragraph">If the email address provided in the previous step is valid and linked to a user account, you should receive an email with reset instructions in the next couple of minutes.</translate></p>
+            <p><translate translate-context="Content/Signup/Paragraph">If the e-mail address provided in the previous step is valid and linked to a user account, you should receive an e-mail with reset instructions in the next couple of minutes.</translate></p>
           </template>
         </form>
         <div v-else class="ui positive message">
diff --git a/front/src/views/auth/ProfileOverview.vue b/front/src/views/auth/ProfileOverview.vue
index 3ed7dde817ea8440b748c42ec3b70baea8dd3a4d..3c0f89eca6f1418ba1b82292d1b6476e0a0537aa 100644
--- a/front/src/views/auth/ProfileOverview.vue
+++ b/front/src/views/auth/ProfileOverview.vue
@@ -30,7 +30,7 @@
         </div>
       </h2>
       <library-widget :url="`federation/actors/${object.full_username}/libraries/`">
-        <translate translate-context="Content/Profile/Paragraph" slot="title">This user shared the following libraries...</translate>
+        <translate translate-context="Content/Profile/Paragraph" slot="title">This user shared the following libraries</translate>
       </library-widget>
     </div>
 
diff --git a/front/src/views/auth/Signup.vue b/front/src/views/auth/Signup.vue
index 9b56d87a774912707b4938b1e5242c3cfffaa66a..0c77f4dafb074f315c18aaaeb5940ab9d45136a7 100644
--- a/front/src/views/auth/Signup.vue
+++ b/front/src/views/auth/Signup.vue
@@ -2,7 +2,7 @@
   <main class="main pusher" v-title="labels.title">
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
-        <h2><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2>
+        <h2><translate translate-context="Content/Signup/Title">Create a Funkwhale account</translate></h2>
         <signup-form :default-invitation="defaultInvitation" :next="next"></signup-form>
       </div>
     </section>
diff --git a/front/src/views/channels/DetailBase.vue b/front/src/views/channels/DetailBase.vue
index 3a00dbea03c99faa1f614e37fd16b7892b95ba37..76506cacf46c0f3b13cbfd110467e80b0cc662f2 100644
--- a/front/src/views/channels/DetailBase.vue
+++ b/front/src/views/channels/DetailBase.vue
@@ -54,7 +54,7 @@
                           <i class="feed icon"></i>
                           <translate translate-context="Content/Channels/Header">Subscribe via RSS</translate>
                         </h3>
-                        <p><translate translate-context="Content/Channels/Label">Copy-paste the following URL in your favorite podcasting app:</translate></p>
+                        <p><translate translate-context="Content/Channels/Label">Copy-paste the following URL in your favorite podcatcher:</translate></p>
                         <copy-input :value="object.rss_url" />
                       </template>
                       <template v-if="object.actor">