diff --git a/aleksis/core/assets/App.vue b/aleksis/core/assets/App.vue
index 1ff35d220a89a2e11f002c24483cadd9786f2c00..af8ec419330c2699fa23718602c5c13d6ac693b3 100644
--- a/aleksis/core/assets/App.vue
+++ b/aleksis/core/assets/App.vue
@@ -1,9 +1,13 @@
 <template>
   <v-app v-cloak>
     <loading
-        v-if="
+      v-if="
         $apollo.loading &&
-        (!currentUser || !whoAmI || !systemProperties || !messages || !footerMenu)
+        (!currentUser ||
+          !whoAmI ||
+          !systemProperties ||
+          !messages ||
+          !footerMenu)
       "
     >
     </loading>
@@ -12,97 +16,103 @@
         <v-list nav dense shaped>
           <v-list-item class="logo">
             <a id="logo-container" href="/" class="brand-logo">
-              <brand-logo :site-preferences="systemProperties.sitePreferences" />
+              <brand-logo
+                :site-preferences="systemProperties.sitePreferences"
+              />
               <!-- FIXME Translation -->
             </a>
           </v-list-item>
           <v-list-item class="search">
-            <sidenav-search/>
+            <sidenav-search />
           </v-list-item>
           <v-list-item-group :value="$route.name">
             <div v-for="menuItem in sideNavMenu" :key="menuItem.name">
               <v-list-group
-                  v-if="menuItem.subMenu.length > 0"
-                  href="#!"
-                  :prepend-icon="menuItem.icon"
-                  :value="$route.matched.slice(-2).shift().name === menuItem.name"
+                v-if="menuItem.subMenu.length > 0"
+                href="#!"
+                :prepend-icon="menuItem.icon"
+                :value="$route.matched.slice(-2).shift().name === menuItem.name"
               >
                 <template #activator>
                   <v-list-item-title
-                  >{{ $t(menuItem.titleKey) }}
+                    >{{ $t(menuItem.titleKey) }}
                   </v-list-item-title>
                 </template>
                 <v-list-item
-                    v-for="subMenuItem in menuItem.subMenu"
-                    :to="{ name: subMenuItem.name }"
-                    :target="subMenuItem.newTab ? '_blank' : '_self'"
-                    :key="subMenuItem.name"
-                    :value="subMenuItem.name"
+                  v-for="subMenuItem in menuItem.subMenu"
+                  :to="{ name: subMenuItem.name }"
+                  :target="subMenuItem.newTab ? '_blank' : '_self'"
+                  :key="subMenuItem.name"
+                  :value="subMenuItem.name"
                 >
                   <v-list-item-icon>
                     <v-icon v-if="subMenuItem.icon"
-                    >{{ subMenuItem.icon }}
+                      >{{ subMenuItem.icon }}
                     </v-icon>
                   </v-list-item-icon>
                   <v-list-item-title
-                  >{{ $t(subMenuItem.titleKey) }}
+                    >{{ $t(subMenuItem.titleKey) }}
                   </v-list-item-title>
                 </v-list-item>
               </v-list-group>
               <v-list-item
-                  v-else
-                  :to="{ name: menuItem.name }"
-                  :target="menuItem.newTab ? '_blank' : '_self'"
-                  :value="menuItem.name"
+                v-else
+                :to="{ name: menuItem.name }"
+                :target="menuItem.newTab ? '_blank' : '_self'"
+                :value="menuItem.name"
               >
                 <v-list-item-icon>
                   <v-icon v-if="menuItem.icon">{{ menuItem.icon }}</v-icon>
                 </v-list-item-icon>
-                <v-list-item-title>{{ $t(menuItem.titleKey) }}</v-list-item-title>
+                <v-list-item-title>{{
+                  $t(menuItem.titleKey)
+                }}</v-list-item-title>
               </v-list-item>
             </div>
           </v-list-item-group>
         </v-list>
 
-        <template v-slot:append>
-          <div class="pa-2">
-            <language-form :available-languages="systemProperties.availableLanguages" />
+        <template #append>
+          <div class="pa-4 d-flex justify-center align-center">
+            <v-spacer />
+            <language-form
+              :available-languages="systemProperties.availableLanguages"
+            />
+            <v-spacer />
           </div>
         </template>
       </v-navigation-drawer>
       <v-app-bar app color="primary white--text">
-        <v-app-bar-nav-icon
-            @click="drawer = !drawer"
-            color="white"/>
+        <v-app-bar-nav-icon @click="drawer = !drawer" color="white" />
 
         <v-toolbar-title
-            tag="a"
-            class="white--text text-decoration-none"
-            href="/"
+          tag="a"
+          class="white--text text-decoration-none"
+          href="/"
         >
           {{ systemProperties.sitePreferences.generalTitle }}
         </v-toolbar-title>
 
-        <v-spacer/>
+        <v-spacer />
         <div v-if="currentUser.isAuthenticated" class="d-flex">
-          <notification-list/>
+          <notification-list />
           <v-menu offset-y>
             <template #activator="{ on, attrs }">
               <v-avatar v-bind="attrs" v-on="on">
                 <img
-                    v-if="
+                  v-if="
                     systemProperties.sitePreferences.accountPersonPreferPhoto &&
                     whoAmI.photo
                   "
-                    :src="whoAmI.photo.url"
-                    :alt="whoAmI.fullName"
-                    :title="whoAmI.fullName"
+                  :src="whoAmI.photo.url"
+                  :alt="whoAmI.fullName"
+                  :title="whoAmI.fullName"
                 />
                 <img
-                    v-else-if="whoAmI.avatarUrl"
-                    :src="whoAmI.avatarUrl"
-                    :alt="whoAmI.fullName + '(' + $t('person.avatar') + ')'"
-                    :title="whoAmI.fullName + '(' + $t('person.avatar') + ')'"
+                  v-else-if="whoAmI.avatarUrl"
+                  :src="whoAmI.avatarUrl"
+                  :alt="whoAmI.fullName + '(' + $t('person.avatar') + ')'"
+                  :title="whoAmI.fullName + '(' + $t('person.avatar') + ')'"
                 />
                 <v-icon v-else>mdi-person</v-icon>
               </v-avatar>
@@ -115,14 +125,14 @@
               <div v-for="menuItem in accountMenu" :key="menuItem.name">
                 <v-divider v-if="menuItem.divider"></v-divider>
                 <v-list-item
-                    :to="{ name: menuItem.name }"
-                    :target="menuItem.newTab ? '_blank' : '_self'"
+                  :to="{ name: menuItem.name }"
+                  :target="menuItem.newTab ? '_blank' : '_self'"
                 >
                   <v-list-item-icon>
                     <v-icon v-if="menuItem.icon">{{ menuItem.icon }}</v-icon>
                   </v-list-item-icon>
                   <v-list-item-title
-                  >{{ $t(menuItem.titleKey) }}
+                    >{{ $t(menuItem.titleKey) }}
                   </v-list-item-title>
                 </v-list-item>
               </div>
@@ -132,56 +142,63 @@
       </v-app-bar>
       <v-main>
         <v-container>
-          <cache-notification/>
+          <cache-notification />
 
           <message-box type="error" v-if="whoAmI && whoAmI.isDummy">
             {{ $t("base.person_is_dummy") }}
           </message-box>
           <message-box
-              type="error"
-              v-else-if="!whoAmI && currentUser.isAnonymous"
+            type="error"
+            v-else-if="!whoAmI && currentUser.isAnonymous"
           >
             {{ $t("base.user_not_linked_to_person") }}
           </message-box>
 
           <div v-if="messages">
             <message-box
-                v-for="(message, idx) in messages"
-                :type="message.tags"
-                :key="idx"
-            >{{ message.message }}
+              v-for="(message, idx) in messages"
+              :type="message.tags"
+              :key="idx"
+              >{{ message.message }}
             </message-box>
           </div>
 
-          <router-view/>
+          <router-view />
         </v-container>
       </v-main>
 
-      <celery-progress-bottom/>
+      <celery-progress-bottom />
 
       <v-footer
-          app
-          absolute
-          inset
-          dark
-          class="pa-0 d-flex"
-          color="primary lighten-1"
+        app
+        absolute
+        inset
+        dark
+        class="pa-0 d-flex"
+        color="primary lighten-1"
       >
         <v-card flat tile class="primary white--text flex-grow-1">
           <v-card-text v-if="footerMenu.items" class="pa-0">
             <v-container class="px-6">
-              <v-row
-                justify="center"
-                no-gutters
-              >
-                <v-btn v-for="menu_item in footerMenu.items" text rounded :href="menu_item.url" color="white" class="ma-2">
-                  <v-icon v-if="menu_item.icon" left>{{ "mdi-" + menu_item.icon }}</v-icon>
+              <v-row justify="center" no-gutters>
+                <v-btn
+                  v-for="menu_item in footerMenu.items"
+                  :key="menu_item.name"
+                  text
+                  rounded
+                  :href="menu_item.url"
+                  color="white"
+                  class="ma-2"
+                >
+                  <v-icon v-if="menu_item.icon" left>{{
+                    "mdi-" + menu_item.icon
+                  }}</v-icon>
                   {{ menu_item.name }}
                 </v-btn>
               </v-row>
             </v-container>
           </v-card-text>
-          <v-divider/>
+          <v-divider />
 
           <v-card-text class="pa-0">
             <v-container class="px-6">
@@ -189,29 +206,29 @@
                 <v-col class="white--text d-flex align-center subtitle-2">
                   <div>
                     <router-link
-                        to="/about"
-                        class="white--text text-decoration-none"
-                    >{{ $t("base.about_aleksis") }}
+                      to="/about"
+                      class="white--text text-decoration-none"
+                      >{{ $t("base.about_aleksis") }}
                     </router-link>
                     <span>© The AlekSIS Team</span>
                   </div>
                 </v-col>
                 <v-col class="d-flex justify-end">
                   <v-btn
-                      v-if="systemProperties.sitePreferences.footerImprintUrl"
-                      small
-                      text
-                      :href="systemProperties.sitePreferences.footerImprintUrl"
-                      color="white"
+                    v-if="systemProperties.sitePreferences.footerImprintUrl"
+                    small
+                    text
+                    :href="systemProperties.sitePreferences.footerImprintUrl"
+                    color="white"
                   >
                     {{ $t("base.imprint") }}
                   </v-btn>
                   <v-btn
-                      v-if="systemProperties.sitePreferences.footerPrivacyUrl"
-                      small
-                      text
-                      :href="systemProperties.sitePreferences.footerPrivacyUrl"
-                      color="white"
+                    v-if="systemProperties.sitePreferences.footerPrivacyUrl"
+                    small
+                    text
+                    :href="systemProperties.sitePreferences.footerPrivacyUrl"
+                    color="white"
                   >
                     {{ $t("base.privacy_policy") }}
                   </v-btn>
@@ -263,16 +280,21 @@ export default {
       for (const route of this.$router.getRoutes()) {
         if (route.meta && route.meta["permission"]) {
           permArray.push(route.meta["permission"]);
-        };
+        }
       }
 
       this.$data.menuPermissionNames = permArray;
 
-      this.$data.accountMenu = this.buildMenu(this.$router.getRoutes(), "inAccountMenu");
-      this.$data.sideNavMenu = this.buildMenu(this.$router.getRoutes(), "inMenu");
+      this.$data.accountMenu = this.buildMenu(
+        this.$router.getRoutes(),
+        "inAccountMenu"
+      );
+      this.$data.sideNavMenu = this.buildMenu(
+        this.$router.getRoutes(),
+        "inMenu"
+      );
     },
     buildMenu(routes, menuKey) {
-
       let menu = {};
 
       // Top-level entries
@@ -291,12 +313,12 @@ export default {
       // Sub menu entries
       for (const route of routes) {
         if (
-            route.name &&
-            route.meta &&
-            route.meta[menuKey] &&
-            route.parent &&
-            route.parent.name &&
-            route.parent.name in menu
+          route.name &&
+          route.meta &&
+          route.meta[menuKey] &&
+          route.parent &&
+          route.parent.name &&
+          route.parent.name in menu
         ) {
           let menuItem = {
             ...route.meta,
@@ -332,7 +354,7 @@ export default {
           name: "footer",
         };
       },
-      update: data => data.customMenuByName
+      update: (data) => data.customMenuByName,
     },
     menuPermissions: {
       query: gqlGlobalPermissions,
@@ -341,7 +363,7 @@ export default {
           permissions: this.$data.menuPermissionNames,
         };
       },
-      update: data => data.globalPermissionsByName
+      update: (data) => data.globalPermissionsByName,
     },
   },
   watch: {
@@ -349,13 +371,13 @@ export default {
       this.$i18n.locale = newProperties.currentLanguage;
       this.$vuetify.lang.current = newProperties.currentLanguage;
       this.$vuetify.theme.themes.light.primary =
-          newProperties.sitePreferences.themePrimary;
+        newProperties.sitePreferences.themePrimary;
       this.$vuetify.theme.themes.light.secondary =
-          newProperties.sitePreferences.themeSecondary;
+        newProperties.sitePreferences.themeSecondary;
       this.$vuetify.theme.themes.dark.primary =
-          newProperties.sitePreferences.themePrimary;
+        newProperties.sitePreferences.themePrimary;
       this.$vuetify.theme.themes.dark.secondary =
-          newProperties.sitePreferences.themeSecondary;
+        newProperties.sitePreferences.themeSecondary;
     },
     whoAmI: function (person) {
       this.$vuetify.theme.dark = person.preferences.themeDesignMode === "dark";
diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js
index 2dc88307919f2ae2e9c5fec558b895750902aa1f..e992ca487ef872b1d0cfc6c2cff89ae616640441 100644
--- a/aleksis/core/assets/app.js
+++ b/aleksis/core/assets/app.js
@@ -77,11 +77,13 @@ const router = new VueRouter({
   mode: "history",
 });
 
-if (document.getElementById('sentry_settings') !== null) {
-  const Sentry = await import("@sentry/vue");
-  const { BrowserTracing } = await import("@sentry/tracing");
+if (document.getElementById("sentry_settings") !== null) {
+  const Sentry = import("@sentry/vue");
+  const { BrowserTracing } = import("@sentry/tracing");
 
-  const sentry_settings = JSON.parse(document.getElementById('sentry_settings').textContent);
+  const sentry_settings = JSON.parse(
+    document.getElementById("sentry_settings").textContent
+  );
   Sentry.init({
     Vue,
     dsn: sentry_settings.dsn,
diff --git a/aleksis/core/assets/components/Error404.vue b/aleksis/core/assets/components/Error404.vue
index f3767ccc320cb3a30af52eb13728b7f0c0c2f970..6c662fe553292dfcb494d6133399a531a7114b63 100644
--- a/aleksis/core/assets/components/Error404.vue
+++ b/aleksis/core/assets/components/Error404.vue
@@ -2,17 +2,19 @@
   <div class="d-flex justify-center align-center">
     <h1 class="mx-2">{{ $t("network_errors.error_404") }}</h1>
 
-    <v-btn class="mx-2" color="primary white--text" @click="$router.push('/')">{{ $t("network_errors.take_me_back") }}</v-btn>
+    <v-btn
+      class="mx-2"
+      color="primary white--text"
+      @click="$router.push('/')"
+      >{{ $t("network_errors.take_me_back") }}</v-btn
+    >
   </div>
 </template>
 
 <script>
-
 export default {
   name: "Error404",
-}
+};
 </script>
 
-<style scoped>
-
-</style>
+<style scoped></style>
diff --git a/aleksis/core/assets/components/LanguageForm.vue b/aleksis/core/assets/components/LanguageForm.vue
index b3eda751c198db8644f4251c2436744aba393f50..7757b9f46622ad0ba3e0158be75f85220a5f0454 100644
--- a/aleksis/core/assets/components/LanguageForm.vue
+++ b/aleksis/core/assets/components/LanguageForm.vue
@@ -7,12 +7,17 @@
     item-value="code"
     menu-props="auto"
     outlined
-    prepend-icon="mdi-translate"
     color="primary"
     single-line
     return-object
+    dense
+    style="width: 75px"
     @input="setLanguage(language)"
-  ></v-select>
+  >
+    <template #selection="{ item, index }">
+      <span class="text-uppercase">{{ item.code }}</span>
+    </template>
+  </v-select>
 </template>
 
 <script>
diff --git a/aleksis/core/assets/components/LegacyBaseTemplate.vue b/aleksis/core/assets/components/LegacyBaseTemplate.vue
index 98ac15e8455671b4e4f5a662ead86a837d5aaf51..bbaefabd0d4fb1fa4e64abd0b21335322b74194b 100644
--- a/aleksis/core/assets/components/LegacyBaseTemplate.vue
+++ b/aleksis/core/assets/components/LegacyBaseTemplate.vue
@@ -30,7 +30,7 @@ export default {
       let qs = [];
       for (const [param, value] of Object.entries(this.$route.query)) {
         qs.push(`${param}=${encodeURIComponent(value)}`);
-      };
+      }
       return "?" + qs.join("&");
     },
   },
diff --git a/aleksis/core/assets/globalPermissions.graphql b/aleksis/core/assets/globalPermissions.graphql
index 934521b48636c6d4dbf800d43e4d881ffdb5947b..0d521e45e26ac6f22bba64d08a1f04b57e59dff2 100644
--- a/aleksis/core/assets/globalPermissions.graphql
+++ b/aleksis/core/assets/globalPermissions.graphql
@@ -1,4 +1,4 @@
-query ($permissions: [String]!){
+query ($permissions: [String]!) {
   globalPermissionsByName(permissions: $permissions) {
     name
     result
diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py
index c0a81bc083573a6b9b2fb4380c5860e660076869..55a217ea986dcbe451c537dfb1e9ad569fa8d05c 100644
--- a/aleksis/core/schema/__init__.py
+++ b/aleksis/core/schema/__init__.py
@@ -12,10 +12,7 @@ from haystack.utils.loading import UnifiedIndex
 from ..models import CustomMenu, Notification, Person, TaskUserAssignment
 from ..util.apps import AppConfig
 from ..util.core_helpers import get_allowed_object_ids, get_app_module, get_app_packages, has_person
-from .celery_progress import (
-    CeleryProgressFetchedMutation,
-    CeleryProgressType,
-)
+from .celery_progress import CeleryProgressFetchedMutation, CeleryProgressType
 from .custom_menu import CustomMenuType
 from .group import GroupType  # noqa
 from .installed_apps import AppType
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 42fdfa4c0992b1fa604dd4d60270ad158efe1915..5131891f94ec5bfde444a65e33ef8473d4156485 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -580,6 +580,7 @@ YARN_INSTALLED_APPS = [
     "rollup-plugin-license@^3.0.1",
     "vue-i18n@8",
     "@sentry/vue@^7.28.0",
+    "prettier@^2.8.1",
     "eslint@^8.26.0",
     "eslint-plugin-vue@^9.7.0",
     "eslint-config-prettier@^8.5.0",
diff --git a/aleksis/core/vite.config.js b/aleksis/core/vite.config.js
index 0e47b88e7e0c63ab64c6639d604d8c08b0983b84..6094093d5b4cd095aefe83f64e281e5a8dfb74b4 100644
--- a/aleksis/core/vite.config.js
+++ b/aleksis/core/vite.config.js
@@ -6,7 +6,7 @@ import vue from "@vitejs/plugin-vue2";
 import { nodeResolve } from "@rollup/plugin-node-resolve";
 import graphql from "@rollup/plugin-graphql";
 import virtual from "@rollup/plugin-virtual";
-const license = require('rollup-plugin-license');
+const license = require("rollup-plugin-license");
 
 const django_values = JSON.parse(fs.readFileSync("./django-vite-values.json"));
 
@@ -50,7 +50,9 @@ export default defineConfig({
           }
 
           // Split each AlekSIS app in its own chunk
-          for (const [appPackage, ep] of Object.entries(django_values.appEntrypoints)) {
+          for (const [appPackage, ep] of Object.entries(
+            django_values.appEntrypoints
+          )) {
             if (id.includes(ep)) {
               return appPackage;
             }
@@ -80,7 +82,7 @@ export default defineConfig({
       },
       thirdParty: {
         allow: {
-          test: 'MIT OR Apache-2.0 OR 0BSD OR BSD-3-Clause',
+          test: "MIT OR Apache-2.0 OR 0BSD OR BSD-3-Clause",
           failOnUnlicensed: true,
           failOnViolation: true,
         },