diff --git a/front/scripts/fix-fomantic-css.py b/front/scripts/fix-fomantic-css.py
index 80b8644166d068ee85b23095955bf95ceac9da9d..e2a9b6b0a4141a2fc040c31b1d9264bd595b3894 100755
--- a/front/scripts/fix-fomantic-css.py
+++ b/front/scripts/fix-fomantic-css.py
@@ -165,6 +165,16 @@ def discard_unused_icons(rule):
         ".wrench",
         ".x",
         ".key",
+        ".cog",
+        ".life.ring",
+        ".language",
+        ".palette",
+        ".sun",
+        ".moon",
+        ".gitlab",
+        ".chevron",
+        ".right",
+        ".left"
     ]
     if ":before" not in rule["lines"][0]:
         return False
diff --git a/front/src/App.vue b/front/src/App.vue
index 5ac09cb8ea9997bed9d7ac906a20a73447862458..2db0fbc262826eeb8c34436a7f32f16440a44627 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -1,35 +1,47 @@
 <template>
-  <div id="app" :key="String($store.state.instance.instanceUrl)" :class="[$store.state.ui.queueFocused ? 'queue-focused' : '', {'has-bottom-player': $store.state.queue.tracks.length > 0}, `is-${ $store.getters['ui/windowSize']}`]">
+  <div
+    id="app"
+    :key="String($store.state.instance.instanceUrl)"
+    :class="[$store.state.ui.queueFocused ? 'queue-focused' : '', {'has-bottom-player': $store.state.queue.tracks.length > 0}, `is-${ $store.getters['ui/windowSize']}`]"
+  >
     <!-- here, we display custom stylesheets, if any -->
     <link
       v-for="url in customStylesheets"
+      :key="url"
       rel="stylesheet"
       property="stylesheet"
       :href="url"
-      :key="url"
     >
-    <template>
-      <sidebar></sidebar>
-      <set-instance-modal @update:show="showSetInstanceModal = $event" :show="showSetInstanceModal"></set-instance-modal>
-      <service-messages></service-messages>
-      <transition name="queue">
-        <queue @touch-progress="$refs.player.setCurrentTime($event)" v-if="$store.state.ui.queueFocused"></queue>
-      </transition>
-      <router-view role="main" :class="{hidden: $store.state.ui.queueFocused}"></router-view>
-      <player ref="player"></player>
-      <app-footer
-        :class="{hidden: $store.state.ui.queueFocused}"
-        :version="version"
-        @show:shortcuts-modal="showShortcutsModal = !showShortcutsModal"
-        @show:set-instance-modal="showSetInstanceModal = !showSetInstanceModal"
-      ></app-footer>
-      <playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
-      <channel-upload-modal v-if="$store.state.auth.authenticated"></channel-upload-modal>
-      <filter-modal v-if="$store.state.auth.authenticated"></filter-modal>
-      <report-modal></report-modal>
-      <shortcuts-modal @update:show="showShortcutsModal = $event" :show="showShortcutsModal"></shortcuts-modal>
-      <GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal"/>
-    </template>
+    <sidebar
+      :width="width"
+      @show:set-instance-modal="showSetInstanceModal = !showSetInstanceModal"
+      @show:shortcuts-modal="showShortcutsModal = !showShortcutsModal"
+    />
+    <set-instance-modal
+      :show="showSetInstanceModal"
+      @update:show="showSetInstanceModal = $event"
+    />
+    <service-messages />
+    <transition name="queue">
+      <queue
+        v-if="$store.state.ui.queueFocused"
+        @touch-progress="$refs.player.setCurrentTime($event)"
+      />
+    </transition>
+    <router-view
+      role="main"
+      :class="{hidden: $store.state.ui.queueFocused}"
+    />
+    <player ref="player" />
+    <playlist-modal v-if="$store.state.auth.authenticated" />
+    <channel-upload-modal v-if="$store.state.auth.authenticated" />
+    <filter-modal v-if="$store.state.auth.authenticated" />
+    <report-modal />
+    <shortcuts-modal
+      :show="showShortcutsModal"
+      @update:show="showShortcutsModal = $event"
+    />
+    <GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal" />
   </div>
 </template>
 
@@ -37,28 +49,26 @@
 import Vue from 'vue'
 import axios from 'axios'
 import _ from '@/lodash'
-import {mapState, mapGetters, mapActions} from 'vuex'
+import { mapState, mapGetters } from 'vuex'
 import { WebSocketBridge } from 'django-channels'
 import GlobalEvents from '@/components/utils/global-events'
-import moment from  'moment'
 import locales from './locales'
-import {getClientOnlyRadio} from '@/radios'
+import { getClientOnlyRadio } from '@/radios'
 
 export default {
-  name: 'app',
+  name: 'App',
   components: {
-    Player:  () => import(/* webpackChunkName: "audio" */ "@/components/audio/Player"),
-    Queue:  () => import(/* webpackChunkName: "audio" */ "@/components/Queue"),
-    PlaylistModal:  () => import(/* webpackChunkName: "auth-audio" */ "@/components/playlists/PlaylistModal"),
-    ChannelUploadModal:  () => import(/* webpackChunkName: "auth-audio" */ "@/components/channels/UploadModal"),
-    Sidebar:  () => import(/* webpackChunkName: "core" */ "@/components/Sidebar"),
-    AppFooter:  () => import(/* webpackChunkName: "core" */ "@/components/Footer"),
-    ServiceMessages:  () => import(/* webpackChunkName: "core" */ "@/components/ServiceMessages"),
-    SetInstanceModal:  () => import(/* webpackChunkName: "core" */ "@/components/SetInstanceModal"),
-    ShortcutsModal:  () => import(/* webpackChunkName: "core" */ "@/components/ShortcutsModal"),
-    FilterModal:  () => import(/* webpackChunkName: "moderation" */ "@/components/moderation/FilterModal"),
-    ReportModal:  () => import(/* webpackChunkName: "moderation" */ "@/components/moderation/ReportModal"),
-    GlobalEvents,
+    Player: () => import(/* webpackChunkName: "audio" */ '@/components/audio/Player'),
+    Queue: () => import(/* webpackChunkName: "audio" */ '@/components/Queue'),
+    PlaylistModal: () => import(/* webpackChunkName: "auth-audio" */ '@/components/playlists/PlaylistModal'),
+    ChannelUploadModal: () => import(/* webpackChunkName: "auth-audio" */ '@/components/channels/UploadModal'),
+    Sidebar: () => import(/* webpackChunkName: "core" */ '@/components/Sidebar'),
+    ServiceMessages: () => import(/* webpackChunkName: "core" */ '@/components/ServiceMessages'),
+    SetInstanceModal: () => import(/* webpackChunkName: "core" */ '@/components/SetInstanceModal'),
+    ShortcutsModal: () => import(/* webpackChunkName: "core" */ '@/components/ShortcutsModal'),
+    FilterModal: () => import(/* webpackChunkName: "moderation" */ '@/components/moderation/FilterModal'),
+    ReportModal: () => import(/* webpackChunkName: "moderation" */ '@/components/moderation/ReportModal'),
+    GlobalEvents
   },
   data () {
     return {
@@ -70,23 +80,168 @@ export default {
       width: window.innerWidth
     }
   },
+  computed: {
+    ...mapState({
+      messages: state => state.ui.messages,
+      nodeinfo: state => state.instance.nodeinfo,
+      playing: state => state.player.playing,
+      bufferProgress: state => state.player.bufferProgress,
+      isLoadingAudio: state => state.player.isLoadingAudio,
+      serviceWorker: state => state.ui.serviceWorker
+    }),
+    ...mapGetters({
+      hasNext: 'queue/hasNext',
+      currentTrack: 'queue/currentTrack',
+      progress: 'player/progress'
+    }),
+    labels () {
+      const play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Play track')
+      const pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Pause track')
+      const next = this.$pgettext('Sidebar/Player/Icon.Tooltip', 'Next track')
+      const expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Expand queue')
+      return {
+        play,
+        pause,
+        next,
+        expandQueue
+      }
+    },
+    suggestedInstances () {
+      const instances = this.$store.state.instance.knownInstances.slice(0)
+      if (this.$store.state.instance.frontSettings.defaultServerUrl) {
+        let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl
+        if (!serverUrl.endsWith('/')) {
+          serverUrl = serverUrl + '/'
+        }
+        instances.push(serverUrl)
+      }
+      instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/')
+      return _.uniq(instances.filter((e) => { return e }))
+    },
+    version () {
+      if (!this.nodeinfo) {
+        return null
+      }
+      return _.get(this.nodeinfo, 'software.version')
+    },
+    customStylesheets () {
+      if (this.$store.state.instance.frontSettings) {
+        return this.$store.state.instance.frontSettings.additionalStylesheets || []
+      }
+      return null
+    }
+  },
+  watch: {
+    '$store.state.instance.instanceUrl' (v) {
+      this.$store.dispatch('instance/fetchSettings')
+      this.fetchNodeInfo()
+    },
+    '$store.state.ui.theme': {
+      immediate: true,
+      handler (newValue, oldValue) {
+        const oldTheme = oldValue || 'light'
+        document.body.classList.remove(`theme-${oldTheme}`)
+        document.body.classList.add(`theme-${newValue}`)
+      }
+    },
+    '$store.state.auth.authenticated' (newValue) {
+      if (!newValue) {
+        this.disconnect()
+      } else {
+        this.openWebsocket()
+      }
+    },
+    '$store.state.ui.currentLanguage': {
+      immediate: true,
+      handler (newValue) {
+        const self = this
+        const htmlLocale = newValue.toLowerCase().replace('_', '-')
+        document.documentElement.setAttribute('lang', htmlLocale)
+        if (newValue === 'en_US') {
+          self.$language.current = 'noop'
+          self.$language.current = newValue
+          return self.$store.commit('ui/momentLocale', 'en')
+        }
+        import(/* webpackChunkName: "locale-[request]" */ `./translations/${newValue}.json`).then((response) => {
+          Vue.$translations[newValue] = response.default[newValue]
+        }).finally(() => {
+          // set current language twice, otherwise we seem to have a cache somewhere
+          // and rendering does not happen
+          self.$language.current = 'noop'
+          self.$language.current = newValue
+        })
+        const momentLocale = newValue.replace('_', '-').toLowerCase()
+        import(/* webpackChunkName: "moment-locale-[request]" */ `moment/locale/${momentLocale}.js`).then(() => {
+          self.$store.commit('ui/momentLocale', momentLocale)
+        }).catch(() => {
+          console.log('No momentjs locale available for', momentLocale)
+          const shortLocale = momentLocale.split('-')[0]
+          import(/* webpackChunkName: "moment-locale-[request]" */ `moment/locale/${shortLocale}.js`).then(() => {
+            self.$store.commit('ui/momentLocale', shortLocale)
+          }).catch(() => {
+            console.log('No momentjs locale available for', shortLocale)
+          })
+        })
+      }
+    },
+    currentTrack: {
+      immediate: true,
+      handler (newValue) {
+        this.updateDocumentTitle()
+      }
+    },
+    '$store.state.ui.pageTitle': {
+      immediate: true,
+      handler (newValue) {
+        this.updateDocumentTitle()
+      }
+    },
+    'serviceWorker.updateAvailable': {
+      handler (v) {
+        if (!v) {
+          return
+        }
+        const self = this
+        this.$store.commit('ui/addMessage', {
+          content: this.$pgettext('App/Message/Paragraph', 'A new version of the app is available.'),
+          date: new Date(),
+          key: 'refreshApp',
+          displayTime: 0,
+          classActions: 'bottom attached opaque',
+          actions: [
+            {
+              text: this.$pgettext('App/Message/Paragraph', 'Update'),
+              class: 'primary',
+              click: function () {
+                self.updateApp()
+              }
+            },
+            {
+              text: this.$pgettext('App/Message/Paragraph', 'Later'),
+              class: 'basic'
+            }
+          ]
+        })
+      },
+      immediate: true
+    }
+  },
   async created () {
-
     if (navigator.serviceWorker) {
       navigator.serviceWorker.addEventListener(
         'controllerchange', () => {
-          if (this.serviceWorker.refreshing) return;
+          if (this.serviceWorker.refreshing) return
           this.$store.commit('ui/serviceWorker', {
             refreshing: true
           })
-          window.location.reload();
+          window.location.reload()
         }
-      );
+      )
     }
-    window.addEventListener('resize', this.handleResize);
-    this.handleResize();
+    window.addEventListener('resize', this.handleResize)
+    this.handleResize()
     this.openWebsocket()
-    let self = this
+    const self = this
     if (!this.$store.state.ui.selectedLanguage) {
       this.autodetectLanguage()
     }
@@ -94,7 +249,7 @@ export default {
       // used to redraw ago dates every minute
       self.$store.commit('ui/computeLastDate')
     }, 1000 * 60)
-    const urlParams = new URLSearchParams(window.location.search);
+    const urlParams = new URLSearchParams(window.location.search)
     const serverUrl = urlParams.get('_server')
     if (serverUrl) {
       this.$store.commit('instance/instanceUrl', serverUrl)
@@ -102,13 +257,12 @@ export default {
     const url = urlParams.get('_url')
     if (url) {
       this.$router.replace(url)
-    }
-    else if (!this.$store.state.instance.instanceUrl) {
+    } else if (!this.$store.state.instance.instanceUrl) {
       // we have several way to guess the API server url. By order of precedence:
       // 1. use the url provided in settings.json, if any
       // 2. use the url specified when building via VUE_APP_INSTANCE_URL
       // 3. use the current url
-      let defaultInstanceUrl = this.$store.state.instance.frontSettings.defaultServerUrl || process.env.VUE_APP_INSTANCE_URL || this.$store.getters['instance/defaultUrl']()
+      const defaultInstanceUrl = this.$store.state.instance.frontSettings.defaultServerUrl || process.env.VUE_APP_INSTANCE_URL || this.$store.getters['instance/defaultUrl']()
       this.$store.commit('instance/instanceUrl', defaultInstanceUrl)
     } else {
       // needed to trigger initialization of axios / service worker
@@ -153,80 +307,78 @@ export default {
     })
   },
   mounted () {
-    let self = this
+    const self = this
     // slight hack to allow use to have internal links in <translate> tags
     // while preserving router behaviour
     document.documentElement.addEventListener('click', function (event) {
-      if (!event.target.matches('a.internal')) return;
+      if (!event.target.matches('a.internal')) return
       self.$router.push(event.target.getAttribute('href'))
-      event.preventDefault();
-    }, false);
+      event.preventDefault()
+    }, false)
     this.$nextTick(() => {
       document.getElementById('fake-content').classList.add('loaded')
     })
-
   },
   destroyed () {
     this.$store.commit('ui/removeWebsocketEventHandler', {
       eventName: 'inbox.item_added',
-      id: 'sidebarCount',
+      id: 'sidebarCount'
     })
     this.$store.commit('ui/removeWebsocketEventHandler', {
       eventName: 'mutation.created',
-      id: 'sidebarReviewEditCount',
+      id: 'sidebarReviewEditCount'
     })
     this.$store.commit('ui/removeWebsocketEventHandler', {
       eventName: 'mutation.updated',
-      id: 'sidebarReviewEditCount',
+      id: 'sidebarReviewEditCount'
     })
     this.$store.commit('ui/removeWebsocketEventHandler', {
       eventName: 'mutation.updated',
-      id: 'sidebarPendingReviewReportCount',
+      id: 'sidebarPendingReviewReportCount'
     })
     this.$store.commit('ui/removeWebsocketEventHandler', {
       eventName: 'user_request.created',
-      id: 'sidebarPendingReviewRequestCount',
+      id: 'sidebarPendingReviewRequestCount'
     })
     this.$store.commit('ui/removeWebsocketEventHandler', {
       eventName: 'Listen',
-      id: 'handleListen',
+      id: 'handleListen'
     })
     this.disconnect()
   },
   methods: {
     incrementNotificationCountInSidebar (event) {
-      this.$store.commit('ui/incrementNotifications', {type: 'inbox', count: 1})
+      this.$store.commit('ui/incrementNotifications', { type: 'inbox', count: 1 })
     },
     incrementReviewEditCountInSidebar (event) {
-      this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewEdits', value: event.pending_review_count})
+      this.$store.commit('ui/incrementNotifications', { type: 'pendingReviewEdits', value: event.pending_review_count })
     },
     incrementPendingReviewReportsCountInSidebar (event) {
-      this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewReports', value: event.unresolved_count})
+      this.$store.commit('ui/incrementNotifications', { type: 'pendingReviewReports', value: event.unresolved_count })
     },
     incrementPendingReviewRequestsCountInSidebar (event) {
-      this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewRequests', value: event.pending_count})
+      this.$store.commit('ui/incrementNotifications', { type: 'pendingReviewRequests', value: event.pending_count })
     },
     handleListen (event) {
       if (this.$store.state.radios.current && this.$store.state.radios.running) {
-        let current = this.$store.state.radios.current
+        const current = this.$store.state.radios.current
         if (current.clientOnly && current.type === 'account') {
           getClientOnlyRadio(current).handleListen(current, event, this.$store)
         }
       }
     },
     async fetchNodeInfo () {
-      let response = await axios.get('instance/nodeinfo/2.0/')
+      const response = await axios.get('instance/nodeinfo/2.0/')
       this.$store.commit('instance/nodeinfo', response.data)
     },
     autodetectLanguage () {
-      let userLanguage = navigator.language || navigator.userLanguage
-      let available = locales.locales.map(e => { return e.code })
-      let self = this
+      const userLanguage = navigator.language || navigator.userLanguage
+      const available = locales.locales.map(e => { return e.code })
       let candidate
-      let matching = available.filter((a) => {
+      const matching = available.filter((a) => {
         return userLanguage.replace('-', '_') === a
       })
-      let almostMatching = available.filter((a) => {
+      const almostMatching = available.filter((a) => {
         return userLanguage.replace('-', '_').split('_')[0] === a.split('_')[0]
       })
       if (matching.length > 0) {
@@ -242,15 +394,15 @@ export default {
       if (!this.bridge) {
         return
       }
-      this.bridge.socket.close(1000, 'goodbye', {keepClosed: true})
+      this.bridge.socket.close(1000, 'goodbye', { keepClosed: true })
     },
     openWebsocket () {
       if (!this.$store.state.auth.authenticated) {
         return
       }
       this.disconnect()
-      let self = this
-      let token = this.$store.state.auth.token
+      const self = this
+      const token = this.$store.state.auth.token
       // let token = 'test'
       const bridge = new WebSocketBridge()
       this.bridge = bridge
@@ -260,7 +412,7 @@ export default {
       bridge.connect(
         url,
         [],
-        {reconnectInterval: 1000 * 60})
+        { reconnectInterval: 1000 * 60 })
       bridge.listen(function (event) {
         self.$store.dispatch('ui/websocketEvent', event)
       })
@@ -268,7 +420,7 @@ export default {
         console.log('Connected to WebSocket')
       })
     },
-    getTrackInformationText(track) {
+    getTrackInformationText (track) {
       const trackTitle = track.title
       const albumArtist = (track.album) ? track.album.artist.name : null
       const artistName = (
@@ -276,11 +428,12 @@ export default {
       const text = `♫ ${trackTitle} – ${artistName} ♫`
       return text
     },
-    updateDocumentTitle() {
-      let parts = []
+    updateDocumentTitle () {
+      const parts = []
       const currentTrackPart = (
-        (this.currentTrack) ? this.getTrackInformationText(this.currentTrack)
-        : null)
+        (this.currentTrack)
+          ? this.getTrackInformationText(this.currentTrack)
+          : null)
       if (currentTrackPart) {
         parts.push(currentTrackPart)
       }
@@ -292,158 +445,13 @@ export default {
     },
 
     updateApp () {
-      this.$store.commit('ui/serviceWorker', {updateAvailable: false})
-      if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return; }
-      this.serviceWorker.registration.waiting.postMessage({command: 'skipWaiting'})
+      this.$store.commit('ui/serviceWorker', { updateAvailable: false })
+      if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return }
+      this.serviceWorker.registration.waiting.postMessage({ command: 'skipWaiting' })
     },
-    handleResize() {
+    handleResize () {
       this.width = window.innerWidth
     }
-  },
-  computed: {
-    ...mapState({
-      messages: state => state.ui.messages,
-      nodeinfo: state => state.instance.nodeinfo,
-      playing: state => state.player.playing,
-      bufferProgress: state => state.player.bufferProgress,
-      isLoadingAudio: state => state.player.isLoadingAudio,
-      serviceWorker: state => state.ui.serviceWorker,
-    }),
-    ...mapGetters({
-      hasNext: "queue/hasNext",
-      currentTrack: 'queue/currentTrack',
-      progress: "player/progress",
-    }),
-    labels() {
-      let play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Play track")
-      let pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Pause track")
-      let next = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Next track")
-      let expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Expand queue")
-      return {
-        play,
-        pause,
-        next,
-        expandQueue,
-      }
-    },
-    suggestedInstances () {
-      let instances = this.$store.state.instance.knownInstances.slice(0)
-      if (this.$store.state.instance.frontSettings.defaultServerUrl) {
-        let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl
-        if (!serverUrl.endsWith('/')) {
-          serverUrl = serverUrl + '/'
-        }
-        instances.push(serverUrl)
-      }
-      instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/')
-      return _.uniq(instances.filter((e) => {return e}))
-    },
-    version () {
-      if (!this.nodeinfo) {
-        return null
-      }
-      return _.get(this.nodeinfo, 'software.version')
-    },
-    customStylesheets () {
-      if (this.$store.state.instance.frontSettings) {
-        return this.$store.state.instance.frontSettings.additionalStylesheets || []
-      }
-    },
-  },
-  watch: {
-    '$store.state.instance.instanceUrl' (v) {
-      this.$store.dispatch('instance/fetchSettings')
-      this.fetchNodeInfo()
-    },
-    '$store.state.ui.theme': {
-      immediate: true,
-      handler (newValue, oldValue) {
-        let oldTheme = oldValue || 'light'
-        document.body.classList.remove(`theme-${oldTheme}`)
-        document.body.classList.add(`theme-${newValue}`)
-      },
-    },
-    '$store.state.auth.authenticated' (newValue) {
-      if (!newValue) {
-        this.disconnect()
-      } else {
-        this.openWebsocket()
-      }
-    },
-    '$store.state.ui.currentLanguage': {
-      immediate: true,
-      handler(newValue) {
-        let self = this
-        let htmlLocale = newValue.toLowerCase().replace('_', '-')
-        document.documentElement.setAttribute('lang', htmlLocale);
-        if (newValue === 'en_US') {
-          self.$language.current = 'noop'
-          self.$language.current = newValue
-          return self.$store.commit('ui/momentLocale', 'en')
-        }
-        import(/* webpackChunkName: "locale-[request]" */ `./translations/${newValue}.json`).then((response) =>{
-          Vue.$translations[newValue] = response.default[newValue]
-        }).finally(() => {
-          // set current language twice, otherwise we seem to have a cache somewhere
-          // and rendering does not happen
-          self.$language.current = 'noop'
-          self.$language.current = newValue
-        })
-        let momentLocale = newValue.replace('_', '-').toLowerCase()
-        import(/* webpackChunkName: "moment-locale-[request]" */ `moment/locale/${momentLocale}.js`).then(() => {
-          self.$store.commit('ui/momentLocale', momentLocale)
-        }).catch(() => {
-          console.log('No momentjs locale available for', momentLocale)
-          let shortLocale = momentLocale.split('-')[0]
-          import(/* webpackChunkName: "moment-locale-[request]" */ `moment/locale/${shortLocale}.js`).then(() => {
-            self.$store.commit('ui/momentLocale', shortLocale)
-          }).catch(() => {
-            console.log('No momentjs locale available for', shortLocale)
-          })
-        })
-      }
-    },
-    'currentTrack': {
-      immediate: true,
-      handler(newValue) {
-        this.updateDocumentTitle()
-      },
-    },
-    '$store.state.ui.pageTitle': {
-      immediate: true,
-      handler(newValue) {
-        this.updateDocumentTitle()
-      },
-    },
-    'serviceWorker.updateAvailable': {
-      handler (v) {
-        if (!v) {
-          return
-        }
-        let self = this
-        this.$store.commit('ui/addMessage', {
-          content: this.$pgettext("App/Message/Paragraph", "A new version of the app is available."),
-          date: new Date(),
-          key: 'refreshApp',
-          displayTime: 0,
-          classActions: 'bottom attached opaque',
-          actions: [
-            {
-              text: this.$pgettext("App/Message/Paragraph", "Update"),
-              class: "primary",
-              click: function () {
-                self.updateApp()
-              },
-            },
-            {
-              text: this.$pgettext("App/Message/Paragraph", "Later"),
-              class: "basic",
-            }
-          ]
-        })
-      },
-      immediate: true,
-    }
   }
 }
 </script>
diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue
index 9713c3cdb940c11810dbd2b5c352bb530e2cce25..b3b2fa6fe3652f846cb40e199a748e18ba9ba704 100644
--- a/front/src/components/Sidebar.vue
+++ b/front/src/components/Sidebar.vue
@@ -1,252 +1,575 @@
 <template>
-<aside :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar', 'component-sidebar']">
-  <header class="ui basic segment header-wrapper">
-    <router-link :title="'Funkwhale'" :to="{name: logoUrl}">
-      <i class="logo bordered inverted vibrant big icon">
-        <logo class="logo"></logo>
-        <span class="visually-hidden">Home</span>
-      </i>
-    </router-link>
-    <router-link v-if="!$store.state.auth.authenticated" class="logo-wrapper" :to="{name: logoUrl}" :title="'Funkwhale'">
-      <img src="../assets/logo/text-white.svg" alt="" />
-    </router-link>
-    <nav class="top ui compact right aligned inverted text menu">
-      <template v-if="$store.state.auth.authenticated">
-
+  <aside :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar', 'component-sidebar']">
+    <header class="ui basic segment header-wrapper">
+      <router-link
+        :title="'Funkwhale'"
+        :to="{name: logoUrl}"
+      >
+        <i class="logo bordered inverted vibrant big icon">
+          <logo class="logo" />
+          <span class="visually-hidden">Home</span>
+        </i>
+      </router-link>
+      <nav class="top ui compact right aligned inverted text menu">
         <div class="right menu">
-          <div class="item" :title="labels.administration" v-if="$store.state.auth.availablePermissions['settings'] || $store.state.auth.availablePermissions['moderation']">
+          <div
+            v-if="$store.state.auth.availablePermissions['settings'] || $store.state.auth.availablePermissions['moderation']"
+            class="item"
+            :title="labels.administration"
+          >
             <div class="item ui inline admin-dropdown dropdown">
-              <i class="wrench icon"></i>
+              <i class="wrench icon" />
               <div
                 v-if="moderationNotifications > 0"
-                :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']">{{ moderationNotifications }}</div>
+                :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']"
+              >
+                {{ moderationNotifications }}
+              </div>
               <div class="menu">
                 <h3 class="header">
-                  <translate translate-context="Sidebar/Admin/Title/Noun">Administration</translate>
+                  <translate translate-context="Sidebar/Admin/Title/Noun">
+                    Administration
+                  </translate>
                 </h3>
-                <div class="divider"></div>
+                <div class="divider" />
                 <router-link
                   v-if="$store.state.auth.availablePermissions['library']"
                   class="item"
-                  :to="{name: 'manage.library.edits', query: {q: 'is_approved:null'}}">
+                  :to="{name: 'manage.library.edits', query: {q: 'is_approved:null'}}"
+                >
                   <div
                     v-if="$store.state.ui.notifications.pendingReviewEdits > 0"
                     :title="labels.pendingReviewEdits"
-                    :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']">
-                    {{ $store.state.ui.notifications.pendingReviewEdits }}</div>
-                  <translate translate-context="*/*/*/Noun">Library</translate>
+                    :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
+                  >
+                    {{ $store.state.ui.notifications.pendingReviewEdits }}
+                  </div>
+                  <translate translate-context="*/*/*/Noun">
+                    Library
+                  </translate>
                 </router-link>
                 <router-link
                   v-if="$store.state.auth.availablePermissions['moderation']"
                   class="item"
-                  :to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}">
+                  :to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}"
+                >
                   <div
                     v-if="$store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests> 0"
                     :title="labels.pendingReviewReports"
-                    :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']">{{ $store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests }}</div>
-                  <translate translate-context="*/Moderation/*">Moderation</translate>
+                    :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
+                  >
+                    {{ $store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests }}
+                  </div>
+                  <translate translate-context="*/Moderation/*">
+                    Moderation
+                  </translate>
                 </router-link>
                 <router-link
                   v-if="$store.state.auth.availablePermissions['settings']"
                   class="item"
-                  :to="{name: 'manage.users.users.list'}">
-                  <translate translate-context="*/*/*/Noun">Users</translate>
+                  :to="{name: 'manage.users.users.list'}"
+                >
+                  <translate translate-context="*/*/*/Noun">
+                    Users
+                  </translate>
                 </router-link>
                 <router-link
                   v-if="$store.state.auth.availablePermissions['settings']"
                   class="item"
-                  :to="{path: '/manage/settings'}">
-                  <translate translate-context="*/*/*/Noun">Settings</translate>
+                  :to="{path: '/manage/settings'}"
+                >
+                  <translate translate-context="*/*/*/Noun">
+                    Settings
+                  </translate>
                 </router-link>
               </div>
             </div>
           </div>
         </div>
         <router-link
-          class="item"
           v-if="$store.state.auth.authenticated"
-          :to="{name: 'content.index'}">
-        <i class="upload icon"></i>
-        <span class="visually-hidden">{{ labels.addContent }}</span>
+          class="item"
+          :to="{name: 'content.index'}"
+        >
+          <i class="upload icon" />
+          <span class="visually-hidden">{{ labels.addContent }}</span>
         </router-link>
-        <router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'notifications'}">
-          <i class="bell icon"></i>
-          <div v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0" :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']">
-            {{ $store.state.ui.notifications.inbox + additionalNotifications }}
+        <template v-if="width > 768">
+          <div class="item">
+            <div class="ui user-dropdown dropdown">
+              <img
+                v-if="$store.state.auth.authenticated && $store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.medium_square_crop"
+                class="ui avatar image"
+                alt=""
+                :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.medium_square_crop)"
+              >
+              <actor-avatar
+                v-else-if="$store.state.auth.authenticated"
+                :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username,}"
+              />
+              <i
+                v-else
+                class="cog icon"
+              />
+              <div
+                v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0"
+                :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']"
+              >
+                {{ $store.state.ui.notifications.inbox + additionalNotifications }}
+              </div>
+              <user-menu
+                :width="width"
+                v-on="$listeners"
+              />
+            </div>
           </div>
-          <span v-else class="visually-hidden">{{ labels.notifications }}</span>
-        </router-link>
-        <div class="item">
-          <div class="ui user-dropdown dropdown" >
-            <img class="ui avatar image" alt="" v-if="$store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.medium_square_crop" :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.medium_square_crop)" />
-            <actor-avatar v-else :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username}" />
-            <div class="menu">
-              <router-link class="item" :to="{name: 'profile.overview', params: {username: $store.state.auth.username}}"><translate translate-context="*/*/*/Noun">Profile</translate></router-link>
-              <router-link class="item" :to="{path: '/settings'}"><translate translate-context="*/*/*/Noun">Settings</translate></router-link>
-              <router-link class="item" :to="{name: 'logout'}"><translate translate-context="Sidebar/Login/List item.Link/Verb">Logout</translate></router-link>
+        </template>
+        <template v-else>
+          <a
+            href=""
+            class="item"
+            @click.prevent.exact="showUserModal = !showUserModal"
+          >
+            <img
+              v-if="$store.state.auth.authenticated && $store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.medium_square_crop"
+              class="ui avatar image"
+              alt=""
+              :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.medium_square_crop)"
+            >
+            <actor-avatar
+              v-else-if="$store.state.auth.authenticated"
+              :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username,}"
+            />
+            <i
+              v-else
+              class="cog icon"
+            />
+            <div
+              v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0"
+              :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']"
+            >
+              {{ $store.state.ui.notifications.inbox + additionalNotifications }}
             </div>
+          </a>
+        </template>
+        <user-modal
+          :show="showUserModal"
+          @showThemeModalEvent="showThemeModal=true"
+          @showLanguageModalEvent="showLanguageModal=true"
+          @update:show="showUserModal = $event"
+        />
+        <modal
+          ref="languageModal"
+          :fullscreen="false"
+          :show="showLanguageModal"
+          @update:show="showLanguageModal = $event"
+        >
+          <i
+            role="button"
+            class="left chevron back inside icon"
+            @click.prevent.exact="showUserModal = !showUserModal"
+          />
+          <div class="header">
+            <h3 class="title">
+              {{ labels.language }}
+            </h3>
           </div>
-        </div>
-      </template>
-      <div class="item collapse-button-wrapper">
-
-        <button
-          @click="isCollapsed = !isCollapsed"
-          :class="['ui', 'basic', 'big', {'vibrant': !isCollapsed}, 'inverted icon', 'collapse', 'button']">
-            <i class="sidebar icon"></i></button>
-      </div>
-    </nav>
-  </header>
-  <div class="ui basic search-wrapper segment">
-    <search-bar @search="isCollapsed = false"></search-bar>
-  </div>
-  <div v-if="!$store.state.auth.authenticated" class="ui basic signup segment">
-    <router-link class="ui fluid tiny primary button" :to="{name: 'login'}"><translate translate-context="*/Login/*/Verb">Login</translate></router-link>
-    <div class="ui small hidden divider"></div>
-    <router-link class="ui fluid tiny button" :to="{path: '/signup'}">
-      <translate translate-context="*/Signup/Link/Verb">Create an account</translate>
-    </router-link>
-  </div>
-  <nav class="secondary" role="navigation" aria-labelledby="navigation-label">
-    <h1 id="navigation-label" class="visually-hidden">
-      <translate translate-context="*/*/*">Main navigation</translate>
-    </h1>
-    <div class="ui small hidden divider"></div>
-    <section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']" :aria-label="labels.mainMenu">
-      <nav class="ui vertical large fluid inverted menu" role="navigation" :aria-label="labels.mainMenu">
-        <div :class="[{collapsed: !exploreExpanded}, 'collapsible item']">
-          <h2 class="header" role="button" @click="exploreExpanded = true" tabindex="0" @focus="exploreExpanded = true">
-            <translate translate-context="*/*/*/Verb">Explore</translate>
-            <i class="angle right icon" v-if="!exploreExpanded"></i>
-          </h2>
-          <div class="menu">
-            <router-link class="item" :to="{name: 'search'}"><i class="search icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Search</translate></router-link>
-            <router-link class="item" :exact="true" :to="{name: 'library.index'}"><i class="music icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Browse</translate></router-link>
-            <router-link class="item" :to="{name: 'library.podcasts.browse'}"><i class="podcast icon"></i><translate translate-context="*/*/*">Podcasts</translate></router-link>
-            <router-link class="item" :to="{name: 'library.albums.browse'}"><i class="compact disc icon"></i><translate translate-context="*/*/*">Albums</translate></router-link>
-            <router-link class="item" :to="{name: 'library.artists.browse'}"><i class="user icon"></i><translate translate-context="*/*/*">Artists</translate></router-link>
-            <router-link class="item" :to="{name: 'library.playlists.browse'}"><i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate></router-link>
-            <router-link class="item" :to="{name: 'library.radios.browse'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
+          <div class="content">
+            <fieldset
+              v-for="(language, key) in $language.available"
+              :key="key"
+            >
+              <input
+                :id="key"
+                v-model="languageSelection"
+                type="radio"
+                name="language"
+                :value="key"
+              >
+              <label :for="key">{{ language }}</label>
+            </fieldset>
           </div>
-        </div>
-        <div :class="[{collapsed: !myLibraryExpanded}, 'collapsible item']" v-if="$store.state.auth.authenticated">
-          <h3 class="header" role="button" @click="myLibraryExpanded = true" tabindex="0" @focus="myLibraryExpanded = true">
-            <translate translate-context="*/*/*/Noun">My Library</translate>
-            <i class="angle right icon" v-if="!myLibraryExpanded"></i>
-          </h3>
-          <div class="menu">
-            <router-link class="item" :exact="true" :to="{name: 'library.me'}"><i class="music icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Browse</translate></router-link>
-            <router-link class="item" :to="{name: 'library.albums.me'}"><i class="compact disc icon"></i><translate translate-context="*/*/*">Albums</translate></router-link>
-            <router-link class="item" :to="{name: 'library.artists.me'}"><i class="user icon"></i><translate translate-context="*/*/*">Artists</translate></router-link>
-            <router-link class="item" :to="{name: 'library.playlists.me'}"><i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate></router-link>
-            <router-link class="item" :to="{name: 'library.radios.me'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
-            <router-link class="item" :to="{name: 'favorites'}"><i class="heart icon"></i><translate translate-context="Sidebar/Favorites/List item.Link/Noun">Favorites</translate></router-link>
+        </modal>
+        <modal
+          ref="themeModal"
+          :fullscreen="false"
+          :show="showThemeModal"
+          @update:show="showThemeModal = $event"
+        >
+          <i
+            role="button"
+            class="left chevron back inside icon"
+            @click.prevent.exact="showUserModal = !showUserModal"
+          />
+          <div class="header">
+            <h3 class="title">
+              {{ labels.theme }}
+            </h3>
           </div>
-        </div>
-        <router-link class="header item" :to="{name: 'subscriptions'}" v-if="$store.state.auth.authenticated">
-          <translate translate-context="*/*/*">Channels</translate>
-        </router-link>
-        <div class="item">
-          <h3 class="header">
-            <translate translate-context="Footer/About/List item.Link">More</translate>
-          </h3>
-          <div class="menu">
-            <router-link class="item" to="/about">
-              <i class="info icon"></i><translate translate-context="Sidebar/*/List item.Link">About this pod</translate>
-            </router-link>
+          <div class="content">
+            <fieldset
+              v-for="theme in themes"
+              :key="theme.key"
+            >
+              <input
+                :id="theme.key"
+                v-model="themeSelection"
+                type="radio"
+                name="theme"
+                :value="theme.key"
+              >
+              <label :for="theme.key">{{ theme.name }}</label>
+            </fieldset>
           </div>
+        </modal>
+        <div class="item collapse-button-wrapper">
+          <button
+            :class="['ui', 'basic', 'big', {'vibrant': !isCollapsed}, 'inverted icon', 'collapse', 'button']"
+            @click="isCollapsed = !isCollapsed"
+          >
+            <i class="sidebar icon" />
+          </button>
         </div>
       </nav>
-    </section>
-  </nav>
-</aside>
+    </header>
+    <div class="ui basic search-wrapper segment">
+      <search-bar @search="isCollapsed = false" />
+    </div>
+    <div
+      v-if="!$store.state.auth.authenticated"
+      class="ui basic signup segment"
+    >
+      <router-link
+        class="ui fluid tiny primary button"
+        :to="{name: 'login'}"
+      >
+        <translate translate-context="*/Login/*/Verb">
+          Login
+        </translate>
+      </router-link>
+      <div class="ui small hidden divider" />
+      <router-link
+        class="ui fluid tiny button"
+        :to="{path: '/signup'}"
+      >
+        <translate translate-context="*/Signup/Link/Verb">
+          Create an account
+        </translate>
+      </router-link>
+    </div>
+    <nav
+      class="secondary"
+      role="navigation"
+      aria-labelledby="navigation-label"
+    >
+      <h1
+        id="navigation-label"
+        class="visually-hidden"
+      >
+        <translate translate-context="*/*/*">
+          Main navigation
+        </translate>
+      </h1>
+      <div class="ui small hidden divider" />
+      <section
+        :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']"
+        :aria-label="labels.mainMenu"
+      >
+        <nav
+          class="ui vertical large fluid inverted menu"
+          role="navigation"
+          :aria-label="labels.mainMenu"
+        >
+          <div :class="[{collapsed: !exploreExpanded}, 'collapsible item']">
+            <h2
+              class="header"
+              role="button"
+              tabindex="0"
+              @click="exploreExpanded = true"
+              @focus="exploreExpanded = true"
+            >
+              <translate translate-context="*/*/*/Verb">
+                Explore
+              </translate>
+              <i
+                v-if="!exploreExpanded"
+                class="angle right icon"
+              />
+            </h2>
+            <div class="menu">
+              <router-link
+                class="item"
+                :to="{name: 'search'}"
+              >
+                <i class="search icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb">
+                  Search
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :exact="true"
+                :to="{name: 'library.index'}"
+              >
+                <i class="music icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb">
+                  Browse
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.podcasts.browse'}"
+              >
+                <i class="podcast icon" /><translate translate-context="*/*/*">
+                  Podcasts
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.albums.browse'}"
+              >
+                <i class="compact disc icon" /><translate translate-context="*/*/*">
+                  Albums
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.artists.browse'}"
+              >
+                <i class="user icon" /><translate translate-context="*/*/*">
+                  Artists
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.playlists.browse'}"
+              >
+                <i class="list icon" /><translate translate-context="*/*/*">
+                  Playlists
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.radios.browse'}"
+              >
+                <i class="feed icon" /><translate translate-context="*/*/*">
+                  Radios
+                </translate>
+              </router-link>
+            </div>
+          </div>
+          <div
+            v-if="$store.state.auth.authenticated"
+            :class="[{collapsed: !myLibraryExpanded}, 'collapsible item']"
+          >
+            <h3
+              class="header"
+              role="button"
+              tabindex="0"
+              @click="myLibraryExpanded = true"
+              @focus="myLibraryExpanded = true"
+            >
+              <translate translate-context="*/*/*/Noun">
+                My Library
+              </translate>
+              <i
+                v-if="!myLibraryExpanded"
+                class="angle right icon"
+              />
+            </h3>
+            <div class="menu">
+              <router-link
+                class="item"
+                :exact="true"
+                :to="{name: 'library.me'}"
+              >
+                <i class="music icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb">
+                  Browse
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.albums.me'}"
+              >
+                <i class="compact disc icon" /><translate translate-context="*/*/*">
+                  Albums
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.artists.me'}"
+              >
+                <i class="user icon" /><translate translate-context="*/*/*">
+                  Artists
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.playlists.me'}"
+              >
+                <i class="list icon" /><translate translate-context="*/*/*">
+                  Playlists
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'library.radios.me'}"
+              >
+                <i class="feed icon" /><translate translate-context="*/*/*">
+                  Radios
+                </translate>
+              </router-link>
+              <router-link
+                class="item"
+                :to="{name: 'favorites'}"
+              >
+                <i class="heart icon" /><translate translate-context="Sidebar/Favorites/List item.Link/Noun">
+                  Favorites
+                </translate>
+              </router-link>
+            </div>
+          </div>
+          <router-link
+            v-if="$store.state.auth.authenticated"
+            class="header item"
+            :to="{name: 'subscriptions'}"
+          >
+            <translate translate-context="*/*/*">
+              Channels
+            </translate>
+          </router-link>
+          <div class="item">
+            <h3 class="header">
+              <translate translate-context="Footer/About/List item.Link">
+                More
+              </translate>
+            </h3>
+            <div class="menu">
+              <router-link
+                class="item"
+                to="/about"
+              >
+                <i class="info icon" /><translate translate-context="Sidebar/*/List item.Link">
+                  About this pod
+                </translate>
+              </router-link>
+            </div>
+          </div>
+          <div
+            v-if="!production"
+            class="item"
+          >
+            <a
+              role="button"
+              href=""
+              class="link item"
+              @click.prevent="$emit('show:set-instance-modal')"
+            >Switch instance</a>
+          </div>
+        </nav>
+      </section>
+    </nav>
+  </aside>
 </template>
 
 <script>
-import { mapState, mapActions, mapGetters } from "vuex"
+import { mapState, mapActions, mapGetters } from 'vuex'
+import UserModal from '@/components/common/UserModal'
+import Logo from '@/components/Logo'
+import SearchBar from '@/components/audio/SearchBar'
+import UserMenu from '@/components/common/UserMenu'
+import Modal from '@/components/semantic/Modal'
 
-import Logo from "@/components/Logo"
-import SearchBar from "@/components/audio/SearchBar"
-
-import $ from "jquery"
+import $ from 'jquery'
 
 export default {
-  name: "sidebar",
+  name: 'Sidebar',
   components: {
     SearchBar,
-    Logo
+    Logo,
+    UserMenu,
+    UserModal,
+    Modal
+  },
+  props: {
+    width: { type: Number, required: true }
   },
-  data() {
+  data () {
     return {
-      selectedTab: "library",
+      selectedTab: 'library',
       isCollapsed: true,
       fetchInterval: null,
       exploreExpanded: false,
       myLibraryExpanded: false,
+      showUserModal: false,
+      showLanguageModal: false,
+      showThemeModal: false,
+      languageSelection: this.$language.current,
+      themeSelection: this.$store.state.ui.theme
     }
   },
-  destroy() {
+  destroy () {
     if (this.fetchInterval) {
       clearInterval(this.fetchInterval)
     }
   },
-  mounted () {
-    this.$nextTick(() => {
-      document.getElementById('fake-sidebar').classList.add('loaded')
-    })
-  },
   computed: {
-    ...mapGetters({
-      additionalNotifications: "ui/additionalNotifications",
-    }),
     ...mapState({
       queue: state => state.queue,
       url: state => state.route.path
     }),
-    labels() {
-      let mainMenu = this.$pgettext('Sidebar/*/Hidden text', "Main menu")
-      let selectTrack = this.$pgettext('Sidebar/Player/Hidden text', "Play this track")
-      let pendingFollows = this.$pgettext('Sidebar/Notifications/Hidden text', "Pending follow requests")
-      let pendingReviewEdits = this.$pgettext('Sidebar/Moderation/Hidden text', "Pending review edits")
+    ...mapGetters({
+      additionalNotifications: 'ui/additionalNotifications'
+    }),
+    labels () {
+      const mainMenu = this.$pgettext('Sidebar/*/Hidden text', 'Main menu')
+      const selectTrack = this.$pgettext('Sidebar/Player/Hidden text', 'Play this track')
+      const pendingFollows = this.$pgettext('Sidebar/Notifications/Hidden text', 'Pending follow requests')
+      const pendingReviewEdits = this.$pgettext('Sidebar/Moderation/Hidden text', 'Pending review edits')
+      const language = this.$pgettext(
+        'Sidebar/Settings/Dropdown.Label/Short, Verb',
+        'Language')
+      const theme = this.$pgettext(
+        'Sidebar/Settings/Dropdown.Label/Short, Verb',
+        'Theme')
       return {
         pendingFollows,
         mainMenu,
         selectTrack,
         pendingReviewEdits,
-        addContent: this.$pgettext("*/Library/*/Verb", 'Add content'),
-        notifications: this.$pgettext("*/Notifications/*", 'Notifications'),
-        administration: this.$pgettext("Sidebar/Admin/Title/Noun", 'Administration'),
+        language,
+        theme,
+        addContent: this.$pgettext('*/Library/*/Verb', 'Add content'),
+        administration: this.$pgettext('Sidebar/Admin/Title/Noun', 'Administration')
       }
     },
-    logoUrl() {
+    logoUrl () {
       if (this.$store.state.auth.authenticated) {
-        return "library.index"
+        return 'library.index'
       } else {
-        return "index"
+        return 'index'
       }
     },
     focusedMenu () {
-      let mapping = {
-        "search": 'exploreExpanded',
-        "library.index": 'exploreExpanded',
-        "library.podcasts.browse": 'exploreExpanded',
-        "library.albums.browse": 'exploreExpanded',
-        "library.albums.detail": 'exploreExpanded',
-        "library.artists.browse": 'exploreExpanded',
-        "library.artists.detail": 'exploreExpanded',
-        "library.tracks.detail": 'exploreExpanded',
-        "library.playlists.browse": 'exploreExpanded',
-        "library.playlists.detail": 'exploreExpanded',
-        "library.radios.browse": 'exploreExpanded',
-        "library.radios.detail": 'exploreExpanded',
-        'library.me': "myLibraryExpanded",
-        'library.albums.me': "myLibraryExpanded",
-        'library.artists.me': "myLibraryExpanded",
-        'library.playlists.me': "myLibraryExpanded",
-        'library.radios.me': "myLibraryExpanded",
-        'favorites': "myLibraryExpanded",
+      const mapping = {
+        search: 'exploreExpanded',
+        'library.index': 'exploreExpanded',
+        'library.podcasts.browse': 'exploreExpanded',
+        'library.albums.browse': 'exploreExpanded',
+        'library.albums.detail': 'exploreExpanded',
+        'library.artists.browse': 'exploreExpanded',
+        'library.artists.detail': 'exploreExpanded',
+        'library.tracks.detail': 'exploreExpanded',
+        'library.playlists.browse': 'exploreExpanded',
+        'library.playlists.detail': 'exploreExpanded',
+        'library.radios.browse': 'exploreExpanded',
+        'library.radios.detail': 'exploreExpanded',
+        'library.me': 'myLibraryExpanded',
+        'library.albums.me': 'myLibraryExpanded',
+        'library.artists.me': 'myLibraryExpanded',
+        'library.playlists.me': 'myLibraryExpanded',
+        'library.radios.me': 'myLibraryExpanded',
+        favorites: 'myLibraryExpanded'
       }
-      let m = mapping[this.$route.name]
+      const m = mapping[this.$route.name]
       if (m) {
         return m
       }
@@ -263,57 +586,31 @@ export default {
         this.$store.state.ui.notifications.pendingReviewReports +
         this.$store.state.ui.notifications.pendingReviewRequests
       )
-    }
-  },
-  methods: {
-    ...mapActions({
-      cleanTrack: "queue/cleanTrack"
-    }),
-    applyContentFilters () {
-      let artistIds = this.$store.getters['moderation/artistFilters']().map((f) => {
-        return f.target.id
-      })
-
-      if (artistIds.length === 0) {
-        return
-      }
-      let self = this
-      let tracks = this.tracks.slice().reverse()
-      tracks.forEach(async (t, i) => {
-        // we loop from the end because removing index from the start can lead to removing the wrong tracks
-        let realIndex = tracks.length - i - 1
-        let matchArtist = artistIds.indexOf(t.artist.id) > -1
-        if (matchArtist) {
-          return await self.cleanTrack(realIndex)
-        }
-        if (t.album && artistIds.indexOf(t.album.artist.id) > -1) {
-          return await self.cleanTrack(realIndex)
-        }
-      })
     },
-    setupDropdown (selector) {
-      let self = this
-      $(self.$el).find(selector).dropdown({
-        selectOnKeydown: false,
-        action: function (text, value, $el) {
-          // used ton ensure focusing the dropdown and clicking via keyboard
-          // works as expected
-          let link = $($el).closest('a')
-          let url = link.attr('href')
-          self.$router.push(url)
-          $(self.$el).find(selector).dropdown('hide')
+    production () {
+      return process.env.NODE_ENV === 'production'
+    },
+    themes () {
+      return [
+        {
+          name: this.$pgettext('Sidebar/Settings/Dropdown.Label/Theme name', 'Light'),
+          key: 'light'
+        },
+        {
+          name: this.$pgettext('Sidebar/Settings/Dropdown.Label/Theme name', 'Dark'),
+          key: 'dark'
         }
-      })
+      ]
     }
   },
   watch: {
-    url: function() {
+    url: function () {
       this.isCollapsed = true
     },
-    "$store.state.moderation.lastUpdate": function () {
+    '$store.state.moderation.lastUpdate': function () {
       this.applyContentFilters()
     },
-    "$store.state.auth.authenticated": {
+    '$store.state.auth.authenticated': {
       immediate: true,
       handler (v) {
         if (v) {
@@ -321,17 +618,21 @@ export default {
             this.setupDropdown('.user-dropdown')
             this.setupDropdown('.admin-dropdown')
           })
+        } else {
+          this.$nextTick(() => {
+            this.setupDropdown('.user-dropdown')
+          })
         }
       }
     },
-    "$store.state.auth.availablePermissions": {
+    '$store.state.auth.availablePermissions': {
       immediate: true,
       handler (v) {
         this.$nextTick(() => {
           this.setupDropdown('.admin-dropdown')
         })
       },
-      deep: true,
+      deep: true
     },
     focusedMenu: {
       immediate: true,
@@ -351,6 +652,93 @@ export default {
         this.myLibraryExpanded = false
       }
     },
+    languageSelection: function (v) {
+      this.$store.dispatch('ui/currentLanguage', v)
+      this.$refs.languageModal.closeModal()
+    },
+    themeSelection: function (v) {
+      this.$store.dispatch('ui/theme', v)
+      this.$refs.themeModal.closeModal()
+    }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      document.getElementById('fake-sidebar').classList.add('loaded')
+    })
+  },
+  methods: {
+    ...mapActions({
+      cleanTrack: 'queue/cleanTrack'
+    }),
+    applyContentFilters () {
+      const artistIds = this.$store.getters['moderation/artistFilters']().map((f) => {
+        return f.target.id
+      })
+
+      if (artistIds.length === 0) {
+        return
+      }
+      const self = this
+      const tracks = this.tracks.slice().reverse()
+      tracks.forEach(async (t, i) => {
+        // we loop from the end because removing index from the start can lead to removing the wrong tracks
+        const realIndex = tracks.length - i - 1
+        const matchArtist = artistIds.indexOf(t.artist.id) > -1
+        if (matchArtist) {
+          return await self.cleanTrack(realIndex)
+        }
+        if (t.album && artistIds.indexOf(t.album.artist.id) > -1) {
+          return await self.cleanTrack(realIndex)
+        }
+      })
+    },
+    setupDropdown (selector) {
+      const self = this
+      $(self.$el).find(selector).dropdown({
+        selectOnKeydown: false,
+        action: function (text, value, $el) {
+          // used ton ensure focusing the dropdown and clicking via keyboard
+          // works as expected
+          const link = $($el).closest('a')
+          const url = link.attr('href')
+          self.$router.push(url)
+          $(self.$el).find(selector).dropdown('hide')
+        }
+      })
+    }
   }
 }
 </script>
+<style>
+[type="radio"] {
+  position: absolute;
+  opacity: 0;
+  cursor: pointer;
+  height: 0;
+  width: 0;
+}
+[type="radio"] + label::after {
+  content: "";
+  font-size: 1.4em;
+}
+[type="radio"]:checked + label::after {
+  margin-left: 10px;
+  content: "\2713"; /* Checkmark */
+  font-size: 1.4em;
+}
+[type="radio"]:checked + label {
+  font-weight: bold;
+}
+fieldset {
+  border: none;
+}
+.back {
+  font-size: 1.25em !important;
+  position: absolute;
+  top: 0.5rem;
+  left: 0.5rem;
+  width: 2.25rem !important;
+  height: 2.25rem !important;
+  padding: 0.625rem 0 0 0;
+}
+</style>
diff --git a/front/src/components/audio/podcast/Modal.vue b/front/src/components/audio/podcast/Modal.vue
index dae3275ccc11a6527c42211ecdbe3d7a1605b570..e1de0cdb10b600b7a08bb10b932b0693721c0bd5 100644
--- a/front/src/components/audio/podcast/Modal.vue
+++ b/front/src/components/audio/podcast/Modal.vue
@@ -1,15 +1,14 @@
 <template>
   <modal
-    @update:show="$emit('update:show', $event)"
+    ref="modal"
     :show="show"
     :scrolling="true"
-    :additionalClasses="['scrolling-track-options']"
+    :additional-classes="['scrolling-track-options']"
+    @update:show="$emit('update:show', $event)"
   >
     <div class="header">
       <div class="ui large centered rounded image">
         <img
-          alt=""
-          class="ui centered image"
           v-if="
             track.album && track.album.cover && track.album.cover.urls.original
           "
@@ -18,43 +17,50 @@
               track.album.cover.urls.medium_square_crop
             )
           "
-        />
-        <img
           alt=""
           class="ui centered image"
+        >
+        <img
           v-else-if="track.cover"
           v-lazy="
             $store.getters['instance/absoluteUrl'](
               track.cover.urls.medium_square_crop
             )
           "
-        />
-        <img
           alt=""
           class="ui centered image"
+        >
+        <img
           v-else-if="track.artist.cover"
           v-lazy="
             $store.getters['instance/absoluteUrl'](
               track.artist.cover.urls.medium_square_crop
             )
           "
-        />
-        <img
           alt=""
           class="ui centered image"
+        >
+        <img
           v-else
+          alt=""
+          class="ui centered image"
           src="../../../assets/audio/default-cover.png"
-        />
+        >
       </div>
-      <h3 class="track-modal-title">{{ track.title }}</h3>
-      <h4 class="track-modal-subtitle">{{ track.artist.name }}</h4>
+      <h3 class="track-modal-title">
+        {{ track.title }}
+      </h3>
+      <h4 class="track-modal-subtitle">
+        {{ track.artist.name }}
+      </h4>
     </div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
     <div class="content">
       <div class="ui one column unstackable grid">
-        <div 
+        <div
+          v-if="$store.state.auth.authenticated && track.artist.content_category !== 'podcast'"
           class="row"
-          v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'">
+        >
           <div
             tabindex="0"
             class="column"
@@ -80,11 +86,11 @@
           <div
             class="column"
             role="button"
+            :aria-label="labels.addToQueue"
             @click.stop.prevent="
               add();
-              closeModal();
+              $refs.modal.closeModal();
             "
-            :aria-label="labels.addToQueue"
           >
             <i class="plus icon track-modal list-icon" />
             <span class="track-modal list-item">{{ labels.addToQueue }}</span>
@@ -94,11 +100,11 @@
           <div
             class="column"
             role="button"
+            :aria-label="labels.playNext"
             @click.stop.prevent="
               addNext(true);
-              closeModal();
+              $refs.modal.closeModal();
             "
-            :aria-label="labels.playNext"
           >
             <i class="step forward icon track-modal list-icon" />
             <span class="track-modal list-item">{{ labels.playNext }}</span>
@@ -108,14 +114,14 @@
           <div
             class="column"
             role="button"
+            :aria-label="labels.startRadio"
             @click.stop.prevent="
               $store.dispatch('radios/start', {
                 type: 'similar',
                 objectId: track.id,
               });
-              closeModal();
+              $refs.modal.closeModal();
             "
-            :aria-label="labels.startRadio"
           >
             <i class="rss icon track-modal list-icon" />
             <span class="track-modal list-item">{{ labels.startRadio }}</span>
@@ -125,8 +131,8 @@
           <div
             class="column"
             role="button"
-            @click.stop="$store.commit('playlists/chooseTrack', track)"
             :aria-label="labels.addToPlaylist"
+            @click.stop="$store.commit('playlists/chooseTrack', track)"
           >
             <i class="list icon track-modal list-icon" />
             <span class="track-modal list-item">{{
@@ -134,8 +140,11 @@
             }}</span>
           </div>
         </div>
-        <div class="ui divider"></div>
-        <div v-if="!isAlbum && track.album" class="row">
+        <div class="ui divider" />
+        <div
+          v-if="!isAlbum && track.album"
+          class="row"
+        >
           <div
             class="column"
             role="button"
@@ -153,7 +162,10 @@
             }}</span>
           </div>
         </div>
-        <div v-if="!isArtist" class="row">
+        <div
+          v-if="!isArtist"
+          class="row"
+        >
           <div
             class="column"
             role="button"
@@ -189,7 +201,7 @@
             }}</span>
           </div>
         </div>
-        <div class="ui divider"></div>
+        <div class="ui divider" />
         <div
           v-for="obj in getReportableObjs({
             track,
@@ -197,16 +209,15 @@
             artist,
           })"
           :key="obj.target.type + obj.target.id"
-          class="row"
           :ref="`report${obj.target.type}${obj.target.id}`"
+          class="row"
           :data-ref="`report${obj.target.type}${obj.target.id}`"
           @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
         >
           <div class="column">
             <i class="share icon track-modal list-icon" /><span
               class="track-modal list-item"
-              >{{ obj.label }}</span
-            >
+            >{{ obj.label }}</span>
           </div>
         </div>
       </div>
@@ -215,90 +226,83 @@
 </template>
 
 <script>
-import Modal from "@/components/semantic/Modal";
-import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
+import Modal from '@/components/semantic/Modal'
 import ReportMixin from '@/components/mixins/Report'
 import PlayOptionsMixin from '@/components/mixins/PlayOptions'
 
 export default {
+  components: {
+    Modal
+  },
   mixins: [ReportMixin, PlayOptionsMixin],
   props: {
     show: { type: Boolean, required: true, default: false },
     track: { type: Object, required: true },
     index: { type: Number, required: true },
     isArtist: { type: Boolean, required: false, default: false },
-    isAlbum: { type: Boolean, required: false, default: false },
+    isAlbum: { type: Boolean, required: false, default: false }
   },
-  components: {
-    Modal,
-    TrackFavoriteIcon,
-  },
-  data() {
+  data () {
     return {
       isShowing: this.show,
       tracks: [this.track],
       album: this.track.album,
-      artist: this.track.artist,
-    };
+      artist: this.track.artist
+    }
   },
   computed: {
-    isFavorite() {
-      return this.$store.getters["favorites/isFavorite"](this.track.id);
+    isFavorite () {
+      return this.$store.getters['favorites/isFavorite'](this.track.id)
     },
-    favoriteButton() {
+    favoriteButton () {
       if (this.isFavorite) {
         return this.$pgettext(
-          "Content/Track/Icon.Tooltip/Verb",
-          "Remove from favorites"
-        );
+          'Content/Track/Icon.Tooltip/Verb',
+          'Remove from favorites'
+        )
       } else {
-        return this.$pgettext("Content/Track/*/Verb", "Add to favorites");
+        return this.$pgettext('Content/Track/*/Verb', 'Add to favorites')
       }
     },
-    trackDetailsButton() {
+    trackDetailsButton () {
       if (this.track.artist.content_category === 'podcast') {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Episode details")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details')
       } else {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Track details")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details')
       }
     },
-    albumDetailsButton() {
+    albumDetailsButton () {
       if (this.track.artist.content_category === 'podcast') {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View series")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series')
       } else {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View album")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album')
       }
     },
-    artistDetailsButton() {
+    artistDetailsButton () {
       if (this.track.artist.content_category === 'podcast') {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View channel")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel')
       } else {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View artist")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist')
       }
     },
-    labels() {
+    labels () {
       return {
         startRadio: this.$pgettext(
-          "*/Queue/Dropdown/Button/Title",
-          "Play radio"
+          '*/Queue/Dropdown/Button/Title',
+          'Play radio'
         ),
-        playNow: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play now"),
+        playNow: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play now'),
         addToQueue: this.$pgettext(
-          "*/Queue/Dropdown/Button/Title",
-          "Add to queue"
+          '*/Queue/Dropdown/Button/Title',
+          'Add to queue'
         ),
-        playNext: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play next"),
+        playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
         addToPlaylist: this.$pgettext(
-          "Sidebar/Player/Icon.Tooltip/Verb",
-          "Add to playlist…"
-        ),
-      };
-    },
-  },
-  methods: {
-    closeModal() {
-      this.$emit("update:show", false);
-    },
-  },
-};
+          'Sidebar/Player/Icon.Tooltip/Verb',
+          'Add to playlist…'
+        )
+      }
+    }
+  }
+}
 </script>
diff --git a/front/src/components/audio/track/Modal.vue b/front/src/components/audio/track/Modal.vue
index dae3275ccc11a6527c42211ecdbe3d7a1605b570..e1de0cdb10b600b7a08bb10b932b0693721c0bd5 100644
--- a/front/src/components/audio/track/Modal.vue
+++ b/front/src/components/audio/track/Modal.vue
@@ -1,15 +1,14 @@
 <template>
   <modal
-    @update:show="$emit('update:show', $event)"
+    ref="modal"
     :show="show"
     :scrolling="true"
-    :additionalClasses="['scrolling-track-options']"
+    :additional-classes="['scrolling-track-options']"
+    @update:show="$emit('update:show', $event)"
   >
     <div class="header">
       <div class="ui large centered rounded image">
         <img
-          alt=""
-          class="ui centered image"
           v-if="
             track.album && track.album.cover && track.album.cover.urls.original
           "
@@ -18,43 +17,50 @@
               track.album.cover.urls.medium_square_crop
             )
           "
-        />
-        <img
           alt=""
           class="ui centered image"
+        >
+        <img
           v-else-if="track.cover"
           v-lazy="
             $store.getters['instance/absoluteUrl'](
               track.cover.urls.medium_square_crop
             )
           "
-        />
-        <img
           alt=""
           class="ui centered image"
+        >
+        <img
           v-else-if="track.artist.cover"
           v-lazy="
             $store.getters['instance/absoluteUrl'](
               track.artist.cover.urls.medium_square_crop
             )
           "
-        />
-        <img
           alt=""
           class="ui centered image"
+        >
+        <img
           v-else
+          alt=""
+          class="ui centered image"
           src="../../../assets/audio/default-cover.png"
-        />
+        >
       </div>
-      <h3 class="track-modal-title">{{ track.title }}</h3>
-      <h4 class="track-modal-subtitle">{{ track.artist.name }}</h4>
+      <h3 class="track-modal-title">
+        {{ track.title }}
+      </h3>
+      <h4 class="track-modal-subtitle">
+        {{ track.artist.name }}
+      </h4>
     </div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
     <div class="content">
       <div class="ui one column unstackable grid">
-        <div 
+        <div
+          v-if="$store.state.auth.authenticated && track.artist.content_category !== 'podcast'"
           class="row"
-          v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'">
+        >
           <div
             tabindex="0"
             class="column"
@@ -80,11 +86,11 @@
           <div
             class="column"
             role="button"
+            :aria-label="labels.addToQueue"
             @click.stop.prevent="
               add();
-              closeModal();
+              $refs.modal.closeModal();
             "
-            :aria-label="labels.addToQueue"
           >
             <i class="plus icon track-modal list-icon" />
             <span class="track-modal list-item">{{ labels.addToQueue }}</span>
@@ -94,11 +100,11 @@
           <div
             class="column"
             role="button"
+            :aria-label="labels.playNext"
             @click.stop.prevent="
               addNext(true);
-              closeModal();
+              $refs.modal.closeModal();
             "
-            :aria-label="labels.playNext"
           >
             <i class="step forward icon track-modal list-icon" />
             <span class="track-modal list-item">{{ labels.playNext }}</span>
@@ -108,14 +114,14 @@
           <div
             class="column"
             role="button"
+            :aria-label="labels.startRadio"
             @click.stop.prevent="
               $store.dispatch('radios/start', {
                 type: 'similar',
                 objectId: track.id,
               });
-              closeModal();
+              $refs.modal.closeModal();
             "
-            :aria-label="labels.startRadio"
           >
             <i class="rss icon track-modal list-icon" />
             <span class="track-modal list-item">{{ labels.startRadio }}</span>
@@ -125,8 +131,8 @@
           <div
             class="column"
             role="button"
-            @click.stop="$store.commit('playlists/chooseTrack', track)"
             :aria-label="labels.addToPlaylist"
+            @click.stop="$store.commit('playlists/chooseTrack', track)"
           >
             <i class="list icon track-modal list-icon" />
             <span class="track-modal list-item">{{
@@ -134,8 +140,11 @@
             }}</span>
           </div>
         </div>
-        <div class="ui divider"></div>
-        <div v-if="!isAlbum && track.album" class="row">
+        <div class="ui divider" />
+        <div
+          v-if="!isAlbum && track.album"
+          class="row"
+        >
           <div
             class="column"
             role="button"
@@ -153,7 +162,10 @@
             }}</span>
           </div>
         </div>
-        <div v-if="!isArtist" class="row">
+        <div
+          v-if="!isArtist"
+          class="row"
+        >
           <div
             class="column"
             role="button"
@@ -189,7 +201,7 @@
             }}</span>
           </div>
         </div>
-        <div class="ui divider"></div>
+        <div class="ui divider" />
         <div
           v-for="obj in getReportableObjs({
             track,
@@ -197,16 +209,15 @@
             artist,
           })"
           :key="obj.target.type + obj.target.id"
-          class="row"
           :ref="`report${obj.target.type}${obj.target.id}`"
+          class="row"
           :data-ref="`report${obj.target.type}${obj.target.id}`"
           @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
         >
           <div class="column">
             <i class="share icon track-modal list-icon" /><span
               class="track-modal list-item"
-              >{{ obj.label }}</span
-            >
+            >{{ obj.label }}</span>
           </div>
         </div>
       </div>
@@ -215,90 +226,83 @@
 </template>
 
 <script>
-import Modal from "@/components/semantic/Modal";
-import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
+import Modal from '@/components/semantic/Modal'
 import ReportMixin from '@/components/mixins/Report'
 import PlayOptionsMixin from '@/components/mixins/PlayOptions'
 
 export default {
+  components: {
+    Modal
+  },
   mixins: [ReportMixin, PlayOptionsMixin],
   props: {
     show: { type: Boolean, required: true, default: false },
     track: { type: Object, required: true },
     index: { type: Number, required: true },
     isArtist: { type: Boolean, required: false, default: false },
-    isAlbum: { type: Boolean, required: false, default: false },
+    isAlbum: { type: Boolean, required: false, default: false }
   },
-  components: {
-    Modal,
-    TrackFavoriteIcon,
-  },
-  data() {
+  data () {
     return {
       isShowing: this.show,
       tracks: [this.track],
       album: this.track.album,
-      artist: this.track.artist,
-    };
+      artist: this.track.artist
+    }
   },
   computed: {
-    isFavorite() {
-      return this.$store.getters["favorites/isFavorite"](this.track.id);
+    isFavorite () {
+      return this.$store.getters['favorites/isFavorite'](this.track.id)
     },
-    favoriteButton() {
+    favoriteButton () {
       if (this.isFavorite) {
         return this.$pgettext(
-          "Content/Track/Icon.Tooltip/Verb",
-          "Remove from favorites"
-        );
+          'Content/Track/Icon.Tooltip/Verb',
+          'Remove from favorites'
+        )
       } else {
-        return this.$pgettext("Content/Track/*/Verb", "Add to favorites");
+        return this.$pgettext('Content/Track/*/Verb', 'Add to favorites')
       }
     },
-    trackDetailsButton() {
+    trackDetailsButton () {
       if (this.track.artist.content_category === 'podcast') {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Episode details")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details')
       } else {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Track details")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details')
       }
     },
-    albumDetailsButton() {
+    albumDetailsButton () {
       if (this.track.artist.content_category === 'podcast') {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View series")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series')
       } else {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View album")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album')
       }
     },
-    artistDetailsButton() {
+    artistDetailsButton () {
       if (this.track.artist.content_category === 'podcast') {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View channel")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel')
       } else {
-        return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View artist")
+        return this.$pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist')
       }
     },
-    labels() {
+    labels () {
       return {
         startRadio: this.$pgettext(
-          "*/Queue/Dropdown/Button/Title",
-          "Play radio"
+          '*/Queue/Dropdown/Button/Title',
+          'Play radio'
         ),
-        playNow: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play now"),
+        playNow: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play now'),
         addToQueue: this.$pgettext(
-          "*/Queue/Dropdown/Button/Title",
-          "Add to queue"
+          '*/Queue/Dropdown/Button/Title',
+          'Add to queue'
         ),
-        playNext: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play next"),
+        playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
         addToPlaylist: this.$pgettext(
-          "Sidebar/Player/Icon.Tooltip/Verb",
-          "Add to playlist…"
-        ),
-      };
-    },
-  },
-  methods: {
-    closeModal() {
-      this.$emit("update:show", false);
-    },
-  },
-};
+          'Sidebar/Player/Icon.Tooltip/Verb',
+          'Add to playlist…'
+        )
+      }
+    }
+  }
+}
 </script>
diff --git a/front/src/components/auth/Settings.vue b/front/src/components/auth/Settings.vue
index 046492fd7004e266998cd5d4e4ae2780d2896166..d614cf4b3f42467c9c14b3be7feb3c78f5c4440b 100644
--- a/front/src/components/auth/Settings.vue
+++ b/front/src/components/auth/Settings.vue
@@ -1,174 +1,353 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <div class="ui vertical stripe segment">
       <section class="ui text container">
         <h2 class="ui header">
-          <translate translate-context="Content/Settings/Title">Account settings</translate>
+          <translate translate-context="Content/Settings/Title">
+            Account settings
+          </translate>
         </h2>
-        <form class="ui form" @submit.prevent="submitSettings()">
-          <div v-if="settings.success" class="ui positive message">
+        <form
+          class="ui form"
+          @submit.prevent="submitSettings()"
+        >
+          <div
+            v-if="settings.success"
+            class="ui positive message"
+          >
             <h4 class="header">
-              <translate translate-context="Content/Settings/Message">Settings updated</translate>
+              <translate translate-context="Content/Settings/Message">
+                Settings updated
+              </translate>
             </h4>
           </div>
-          <div v-if="settings.errors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Settings/Error message.Title">Your settings can't be updated</translate></h4>
+          <div
+            v-if="settings.errors.length > 0"
+            role="alert"
+            class="ui negative message"
+          >
+            <h4 class="header">
+              <translate translate-context="Content/Settings/Error message.Title">
+                Your settings can't be updated
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in settings.errors">{{ error }}</li>
+              <li
+                v-for="(error, key) in settings.errors"
+                :key="key"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
-          <div class="field" v-for="f in orderedSettingsFields">
+          <div
+            v-for="f in orderedSettingsFields"
+            :key="f.id"
+            class="field"
+          >
             <label :for="f.id">{{ sharedLabels.fields[f.id].label }}</label>
-            <p v-if="f.help">{{ sharedLabels.fields[f.id].help }}</p>
-            <select :id="f.id" v-if="f.type === 'dropdown'" class="ui dropdown" v-model="f.value">
-              <option :value="c" v-for="c in f.choices">{{ sharedLabels.fields[f.id].choices[c] }}</option>
+            <p v-if="f.help">
+              {{ sharedLabels.fields[f.id].help }}
+            </p>
+            <select
+              v-if="f.type === 'dropdown'"
+              :id="f.id"
+              v-model="f.value"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(c, key) in f.choices"
+                :key="key"
+                :value="c"
+              >
+                {{ sharedLabels.fields[f.id].choices[c] }}
+              </option>
             </select>
-            <content-form :field-id="f.id" v-if="f.type === 'content'" v-model="f.value.text"></content-form>
+            <content-form
+              v-if="f.type === 'content'"
+              v-model="f.value.text"
+              :field-id="f.id"
+            />
           </div>
-          <button :class="['ui', {'loading': isLoading}, 'button']" type="submit">
-            <translate translate-context="Content/Settings/Button.Label/Verb">Update settings</translate>
+          <button
+            :class="['ui', {'loading': isLoading}, 'button']"
+            type="submit"
+          >
+            <translate translate-context="Content/Settings/Button.Label/Verb">
+              Update settings
+            </translate>
           </button>
         </form>
       </section>
       <section class="ui text container">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <translate translate-context="Content/Settings/Title">Avatar</translate>
+          <translate translate-context="Content/Settings/Title">
+            Avatar
+          </translate>
         </h2>
         <div class="ui form">
-          <div v-if="avatarErrors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Settings/Error message.Title">Your avatar cannot be saved</translate></h4>
+          <div
+            v-if="avatarErrors.length > 0"
+            role="alert"
+            class="ui negative message"
+          >
+            <h4 class="header">
+              <translate translate-context="Content/Settings/Error message.Title">
+                Your avatar cannot be saved
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in avatarErrors">{{ error }}</li>
+              <li
+                v-for="(error, key) in avatarErrors"
+                :key="key"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
           {{ }}
           <attachment-input
             :value="avatar.uuid"
-            @input="submitAvatar($event)"
             :initial-value="initialAvatar"
             :required="false"
-            @delete="avatar = {uuid: null}">
-            <translate translate-context="Content/Channel/*" slot="label">Avatar</translate>
-            </attachment-input>
+            @input="submitAvatar($event)"
+            @delete="avatar = {uuid: null}"
+          >
+            <translate
+              slot="label"
+              translate-context="Content/Channel/*"
+            >
+              Avatar
+            </translate>
+          </attachment-input>
         </div>
       </section>
 
       <section class="ui text container">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <translate translate-context="Content/Settings/Title/Verb">Change my password</translate>
+          <translate translate-context="Content/Settings/Title/Verb">
+            Change my password
+          </translate>
         </h2>
         <div class="ui message">
-          <translate translate-context="Content/Settings/Paragraph'">Changing your password will also change your Subsonic API password if you have requested one.</translate>&nbsp;<translate translate-context="Content/Settings/Paragraph">You will have to update your password on your clients that use this password.</translate>
+          <translate translate-context="Content/Settings/Paragraph'">
+            Changing your password will also change your Subsonic API password if you have requested one.
+          </translate>&nbsp;<translate translate-context="Content/Settings/Paragraph">
+            You will have to update your password on your clients that use this password.
+          </translate>
         </div>
-        <form class="ui form" @submit.prevent="submitPassword()">
-          <div v-if="passwordError" role="alert" class="ui negative message">
+        <form
+          class="ui form"
+          @submit.prevent="submitPassword()"
+        >
+          <div
+            v-if="passwordError"
+            role="alert"
+            class="ui negative message"
+          >
             <h4 class="header">
-              <translate translate-context="Content/Settings/Error message.Title">Your password cannot be changed</translate>
+              <translate translate-context="Content/Settings/Error message.Title">
+                Your password cannot be changed
+              </translate>
             </h4>
             <ul class="list">
-              <li v-if="passwordError == 'invalid_credentials'"><translate translate-context="Content/Settings/Error message.List item/Call to action">Please double-check your password is correct</translate></li>
+              <li v-if="passwordError == 'invalid_credentials'">
+                <translate translate-context="Content/Settings/Error message.List item/Call to action">
+                  Please double-check your password is correct
+                </translate>
+              </li>
             </ul>
           </div>
           <div class="field">
             <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" />
+            <password-input
+              v-model="old_password"
+              field-id="old-password-field"
+              required
+            />
           </div>
           <div class="field">
             <label for="new-password-field"><translate translate-context="Content/Settings/Input.Label">New password</translate></label>
-            <password-input field-id="new-password-field" required v-model="new_password" />
+            <password-input
+              v-model="new_password"
+              field-id="new-password-field"
+              required
+            />
           </div>
           <dangerous-button
             :class="['ui', {'loading': isLoading}, {disabled: !new_password || !old_password}, 'warning', 'button']"
-            :action="submitPassword">
-            <translate translate-context="Content/Settings/Button.Label">Change password</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Change your password?</translate></p>
+            :action="submitPassword"
+          >
+            <translate translate-context="Content/Settings/Button.Label">
+              Change password
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Settings/Title">
+                Change your password?
+              </translate>
+            </p>
             <div slot="modal-content">
-              <p><translate translate-context="Popup/Settings/Paragraph">Changing your password will have the following consequences:</translate></p>
+              <p>
+                <translate translate-context="Popup/Settings/Paragraph">
+                  Changing your password will have the following consequences:
+                </translate>
+              </p>
               <ul>
-                <li><translate translate-context="Popup/Settings/List item">You will be logged out from this session and have to log in with the new one</translate></li>
-                <li><translate translate-context="Popup/Settings/List item">Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password</translate></li>
+                <li>
+                  <translate translate-context="Popup/Settings/List item">
+                    You will be logged out from this session and have to log in with the new one
+                  </translate>
+                </li>
+                <li>
+                  <translate translate-context="Popup/Settings/List item">
+                    Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password
+                  </translate>
+                </li>
               </ul>
             </div>
-            <div slot="modal-confirm"><translate translate-context="Popup/Settings/Button.Label">Disable access</translate></div>
+            <div slot="modal-confirm">
+              <translate translate-context="Popup/Settings/Button.Label">
+                Disable access
+              </translate>
+            </div>
           </dangerous-button>
         </form>
         <div class="ui hidden divider" />
         <subsonic-token-form />
       </section>
 
-      <section class="ui text container" id="content-filters">
-        <div class="ui hidden divider"></div>
+      <section
+        id="content-filters"
+        class="ui text container"
+      >
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <i class="eye slash outline icon"></i>
+          <i class="eye slash outline icon" />
           <div class="content">
-            <translate translate-context="Content/Settings/Title/Noun">Content filters</translate>
+            <translate translate-context="Content/Settings/Title/Noun">
+              Content filters
+            </translate>
           </div>
         </h2>
-        <p><translate translate-context="Content/Settings/Paragraph">Content filters help you hide content you don't want to see on the service.</translate></p>
+        <p>
+          <translate translate-context="Content/Settings/Paragraph">
+            Content filters help you hide content you don't want to see on the service.
+          </translate>
+        </p>
 
         <button
+          class="ui icon button"
           @click="$store.dispatch('moderation/fetchContentFilters')"
-          class="ui icon button">
-          <i class="refresh icon"></i>&nbsp;
-          <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate>
+        >
+          <i class="refresh icon" />&nbsp;
+          <translate translate-context="Content/*/Button.Label/Short, Verb">
+            Refresh
+          </translate>
         </button>
         <h3 class="ui header">
-          <translate translate-context="Content/Settings/Title">Hidden artists</translate>
+          <translate translate-context="Content/Settings/Title">
+            Hidden artists
+          </translate>
         </h3>
         <table class="ui compact very basic unstackable table">
           <thead>
             <tr>
-              <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-              <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
-              <th></th>
+              <th>
+                <translate translate-context="*/*/*/Noun">
+                  Name
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="Content/*/*/Noun">
+                  Creation date
+                </translate>
+              </th>
+              <th />
             </tr>
           </thead>
           <tbody>
-            <tr v-for="filter in $store.getters['moderation/artistFilters']()" :key='filter.uuid'>
+            <tr
+              v-for="filter in $store.getters['moderation/artistFilters']()"
+              :key="filter.uuid"
+            >
               <td>
                 <router-link :to="{name: 'library.artists.detail', params: {id: filter.target.id }}">
                   {{ filter.target.name }}
                 </router-link>
               </td>
               <td>
-                <human-date :date="filter.creation_date"></human-date>
+                <human-date :date="filter.creation_date" />
               </td>
               <td>
-                <button @click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)" class="ui basic tiny button">
-                  <translate translate-context="*/*/*/Verb">Delete</translate>
+                <button
+                  class="ui basic tiny button"
+                  @click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)"
+                >
+                  <translate translate-context="*/*/*/Verb">
+                    Delete
+                  </translate>
                 </button>
               </td>
             </tr>
           </tbody>
         </table>
       </section>
-      <section class="ui text container" id="grants">
-        <div class="ui hidden divider"></div>
+      <section
+        id="grants"
+        class="ui text container"
+      >
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <i class="open lock icon"></i>
+          <i class="open lock icon" />
           <div class="content">
-            <translate translate-context="Content/Settings/Title/Noun">Authorized apps</translate>
+            <translate translate-context="Content/Settings/Title/Noun">
+              Authorized apps
+            </translate>
           </div>
         </h2>
-        <p><translate translate-context="Content/Settings/Paragraph">This is the list of applications that have access to your account data.</translate></p>
+        <p>
+          <translate translate-context="Content/Settings/Paragraph">
+            This is the list of applications that have access to your account data.
+          </translate>
+        </p>
         <button
+          class="ui icon button"
           @click="fetchApps()"
-          class="ui icon button">
-          <i class="refresh icon"></i>&nbsp;
-          <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate>
+        >
+          <i class="refresh icon" />&nbsp;
+          <translate translate-context="Content/*/Button.Label/Short, Verb">
+            Refresh
+          </translate>
         </button>
-        <table v-if="apps.length > 0" class="ui compact very basic unstackable table">
+        <table
+          v-if="apps.length > 0"
+          class="ui compact very basic unstackable table"
+        >
           <thead>
             <tr>
-              <th><translate translate-context="*/*/*/Noun">Application</translate></th>
-              <th><translate translate-context="Content/*/*/Noun">Permissions</translate></th>
-              <th></th>
+              <th>
+                <translate translate-context="*/*/*/Noun">
+                  Application
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="Content/*/*/Noun">
+                  Permissions
+                </translate>
+              </th>
+              <th />
             </tr>
           </thead>
           <tbody>
-            <tr v-for="app in apps" :key='app.client_id'>
+            <tr
+              v-for="app in apps"
+              :key="app.client_id"
+            >
               <td>
                 {{ app.name }}
               </td>
@@ -178,18 +357,38 @@
               <td>
                 <dangerous-button
                   class="ui tiny danger button"
-                  @confirm="revokeApp(app.client_id)">
-                  <translate translate-context="*/*/*/Verb">Revoke</translate>
-                  <p slot="modal-header" v-translate="{application: app.name}" translate-context="Popup/Settings/Title">Revoke access for application "%{ application }"?</p>
-                  <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will prevent this application from accessing the service on your behalf.</translate></p>
-                  <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Revoke access</translate></div>
+                  @confirm="revokeApp(app.client_id)"
+                >
+                  <translate translate-context="*/*/*/Verb">
+                    Revoke
+                  </translate>
+                  <p
+                    slot="modal-header"
+                    v-translate="{application: app.name}"
+                    translate-context="Popup/Settings/Title"
+                  >
+                    Revoke access for application "%{ application }"?
+                  </p>
+                  <p slot="modal-content">
+                    <translate translate-context="Popup/Settings/Paragraph">
+                      This will prevent this application from accessing the service on your behalf.
+                    </translate>
+                  </p>
+                  <div slot="modal-confirm">
+                    <translate translate-context="*/Settings/Button.Label/Verb">
+                      Revoke access
+                    </translate>
+                  </div>
                 </dangerous-button>
               </td>
             </tr>
           </tbody>
         </table>
         <empty-state v-else>
-          <translate slot="title" translate-context="Content/Applications/Paragraph">
+          <translate
+            slot="title"
+            translate-context="Content/Applications/Paragraph"
+          >
             You don't have any application connected with your account.
           </translate>
           <translate translate-context="Content/Applications/Paragraph">
@@ -197,29 +396,61 @@
           </translate>
         </empty-state>
       </section>
-      <section class="ui text container" id="apps">
-        <div class="ui hidden divider"></div>
+      <section
+        id="apps"
+        class="ui text container"
+      >
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <i class="code icon"></i>
+          <i class="code icon" />
           <div class="content">
-            <translate translate-context="Content/Settings/Title/Noun">Your applications</translate>
+            <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 registered.</translate></p>
-        <router-link class="ui success button" :to="{name: 'settings.applications.new'}">
-          <translate translate-context="Content/Settings/Button.Label">Register a new application</translate>
+        <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">
+            Register a new application
+          </translate>
         </router-link>
-        <table v-if="ownedApps.length > 0" class="ui compact very basic unstackable table">
+        <table
+          v-if="ownedApps.length > 0"
+          class="ui compact very basic unstackable table"
+        >
           <thead>
             <tr>
-              <th><translate translate-context="*/*/*/Noun">Application</translate></th>
-              <th><translate translate-context="Content/*/*/Noun">Scopes</translate></th>
-              <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
-              <th></th>
+              <th>
+                <translate translate-context="*/*/*/Noun">
+                  Application
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="Content/*/*/Noun">
+                  Scopes
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="Content/*/*/Noun">
+                  Creation date
+                </translate>
+              </th>
+              <th />
             </tr>
           </thead>
           <tbody>
-            <tr v-for="app in ownedApps" :key='app.client_id'>
+            <tr
+              v-for="app in ownedApps"
+              :key="app.client_id"
+            >
               <td>
                 <router-link :to="{name: 'settings.applications.edit', params: {id: app.client_id}}">
                   {{ app.name }}
@@ -232,23 +463,48 @@
                 <human-date :date="app.created" />
               </td>
               <td>
-                <router-link class="ui tiny success button" :to="{name: 'settings.applications.edit', params: {id: app.client_id}}">
-                  <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                <router-link
+                  class="ui tiny success button"
+                  :to="{name: 'settings.applications.edit', params: {id: app.client_id}}"
+                >
+                  <translate translate-context="Content/*/Button.Label/Verb">
+                    Edit
+                  </translate>
                 </router-link>
                 <dangerous-button
                   class="ui tiny danger button"
-                  @confirm="deleteApp(app.client_id)">
-                  <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>
+                  @confirm="deleteApp(app.client_id)"
+                >
+                  <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>
           </tbody>
         </table>
         <empty-state v-else>
-          <translate slot="title" translate-context="Content/Applications/Paragraph">
+          <translate
+            slot="title"
+            translate-context="Content/Applications/Paragraph"
+          >
             You don't have registered any application yet.
           </translate>
           <translate translate-context="Content/Applications/Paragraph">
@@ -257,85 +513,181 @@
         </empty-state>
       </section>
 
-      <section class="ui text container" id="plugins">
-        <div class="ui hidden divider"></div>
+      <section
+        id="plugins"
+        class="ui text container"
+      >
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <i class="code icon"></i>
+          <i class="code icon" />
           <div class="content">
-            <translate translate-context="Content/Settings/Title/Noun">Plugins</translate>
+            <translate translate-context="Content/Settings/Title/Noun">
+              Plugins
+            </translate>
           </div>
         </h2>
-        <p><translate translate-context="Content/Settings/Paragraph">Use plugins to extend Funkwhale and get additional features.</translate></p>
-        <router-link class="ui success button" :to="{name: 'settings.plugins'}">
-          <translate translate-context="Content/Settings/Button.Label">Manage plugins</translate>
+        <p>
+          <translate translate-context="Content/Settings/Paragraph">
+            Use plugins to extend Funkwhale and get additional features.
+          </translate>
+        </p>
+        <router-link
+          class="ui success button"
+          :to="{name: 'settings.plugins'}"
+        >
+          <translate translate-context="Content/Settings/Button.Label">
+            Manage plugins
+          </translate>
         </router-link>
       </section>
       <section class="ui text container">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <i class="comment icon"></i>
+          <i class="comment icon" />
           <div class="content">
-            <translate translate-context="*/*/Button.Label">Change my e-mail address</translate>
+            <translate translate-context="*/*/Button.Label">
+              Change my e-mail address
+            </translate>
           </div>
         </h2>
         <p>
-          <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>
+          <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 e-mail 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 e-mail address</translate></h4>
+        <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 e-mail address
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in changeEmailErrors">{{ error }}</li>
+              <li
+                v-for="(error, key) in changeEmailErrors"
+                :key="key"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
           <div class="field">
             <label for="new-email"><translate translate-context="*/*/*">New e-mail address</translate></label>
-            <input id="new-email" required v-model="newEmail" type="email" />
+            <input
+              id="new-email"
+              v-model="newEmail"
+              required
+              type="email"
+            >
           </div>
           <div class="field">
             <label for="current-password-field-email"><translate translate-context="*/*/*">Password</translate></label>
-            <password-input field-id="current-password-field-email" required v-model="emailPassword" />
+            <password-input
+              v-model="emailPassword"
+              field-id="current-password-field-email"
+              required
+            />
           </div>
-          <button type="submit" class="ui button"><translate translate-context="*/*/*">Update</translate></button>
+          <button
+            type="submit"
+            class="ui button"
+          >
+            <translate translate-context="*/*/*">
+              Update
+            </translate>
+          </button>
         </form>
       </section>
       <section class="ui text container">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <h2 class="ui header">
-          <i class="trash icon"></i>
+          <i class="trash icon" />
           <div class="content">
-            <translate translate-context="*/*/Button.Label">Delete my account</translate>
+            <translate translate-context="*/*/Button.Label">
+              Delete my account
+            </translate>
           </div>
         </h2>
         <p>
-          <translate translate-context="Content/Settings/Paragraph'">You can permanently and irreversibly delete your account and all the associated data using the form below. You will be asked for confirmation.</translate>
+          <translate translate-context="Content/Settings/Paragraph'">
+            You can permanently and irreversibly delete your account and all the associated data using the form below. You will be asked for confirmation.
+          </translate>
         </p>
-        <div role="alert" class="ui warning message">
-          <translate translate-context="Content/Settings/Paragraph'">Your account will be deleted from our servers within a few minutes. We will also notify other servers who may have a copy of some of your data so they can proceed to deletion. Please note that some of these servers may be offline or unwilling to comply though.</translate>
+        <div
+          role="alert"
+          class="ui warning message"
+        >
+          <translate translate-context="Content/Settings/Paragraph'">
+            Your account will be deleted from our servers within a few minutes. We will also notify other servers who may have a copy of some of your data so they can proceed to deletion. Please note that some of these servers may be offline or unwilling to comply though.
+          </translate>
         </div>
         <div class="ui form">
-          <div v-if="accountDeleteErrors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Settings/Error message.Title">We cannot delete your account</translate></h4>
+          <div
+            v-if="accountDeleteErrors.length > 0"
+            role="alert"
+            class="ui negative message"
+          >
+            <h4 class="header">
+              <translate translate-context="Content/Settings/Error message.Title">
+                We cannot delete your account
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in accountDeleteErrors">{{ error }}</li>
+              <li
+                v-for="(error, key) in accountDeleteErrors"
+                :key="key"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
           <div class="field">
             <label for="current-password-field"><translate translate-context="*/*/*">Password</translate></label>
-            <password-input field-id="current-password-field" required v-model="password" />
+            <password-input
+              v-model="password"
+              field-id="current-password-field"
+              required
+            />
           </div>
           <dangerous-button
             :class="['ui', {'loading': isDeletingAccount}, {disabled: !password}, {danger: password}, 'button']"
-            :action="deleteAccount">
-            <translate translate-context="*/*/Button.Label">Delete my account…</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Do you want to delete your account?</translate></p>
+            :action="deleteAccount"
+          >
+            <translate translate-context="*/*/Button.Label">
+              Delete my account…
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Settings/Title">
+                Do you want to delete your account?
+              </translate>
+            </p>
             <div slot="modal-content">
-              <p><translate translate-context="Popup/Settings/Paragraph">This is irreversible and will permanently remove your data from our servers. You will we immediatly logged out.</translate></p>
+              <p>
+                <translate translate-context="Popup/Settings/Paragraph">
+                  This is irreversible and will permanently remove your data from our servers. You will we immediatly logged out.
+                </translate>
+              </p>
+            </div>
+            <div slot="modal-confirm">
+              <translate translate-context="*/*/Button.Label">
+                Delete my account
+              </translate>
             </div>
-            <div slot="modal-confirm"><translate translate-context="*/*/Button.Label">Delete my account</translate></div>
           </dangerous-button>
         </div>
       </section>
@@ -344,30 +696,30 @@
 </template>
 
 <script>
-import $ from "jquery"
-import axios from "axios"
-import logger from "@/logging"
-import PasswordInput from "@/components/forms/PasswordInput"
-import SubsonicTokenForm from "@/components/auth/SubsonicTokenForm"
-import TranslationsMixin from "@/components/mixins/Translations"
+import $ from 'jquery'
+import axios from 'axios'
+import logger from '@/logging'
+import PasswordInput from '@/components/forms/PasswordInput'
+import SubsonicTokenForm from '@/components/auth/SubsonicTokenForm'
+import TranslationsMixin from '@/components/mixins/Translations'
 import AttachmentInput from '@/components/common/AttachmentInput'
 
 export default {
-  mixins: [TranslationsMixin],
   components: {
     PasswordInput,
     SubsonicTokenForm,
     AttachmentInput
   },
-  data() {
-    let d = {
+  mixins: [TranslationsMixin],
+  data () {
+    const d = {
       // We need to initialize the component with any
       // properties that will be used in it
-      old_password: "",
-      new_password: "",
-      avatar: {...(this.$store.state.auth.profile.avatar || {uuid: null})},
-      passwordError: "",
-      password: "",
+      old_password: '',
+      new_password: '',
+      avatar: { ...(this.$store.state.auth.profile.avatar || { uuid: null }) },
+      passwordError: '',
+      password: '',
       isLoading: false,
       isLoadingAvatar: false,
       isDeletingAccount: false,
@@ -382,16 +734,16 @@ export default {
       settings: {
         success: false,
         errors: [],
-        order: ["summary", "privacy_level"],
+        order: ['summary', 'privacy_level'],
         fields: {
           summary: {
-            type: "content",
-            initial: this.$store.state.auth.profile.summary || {text: '', content_type: 'text/markdown'},
+            type: 'content',
+            initial: this.$store.state.auth.profile.summary || { text: '', content_type: 'text/markdown' }
           },
           privacy_level: {
-            type: "dropdown",
+            type: 'dropdown',
             initial: this.$store.state.auth.profile.privacy_level,
-            choices: ["me", "instance", "everyone"]
+            choices: ['me', 'instance', 'everyone']
           }
         }
       }
@@ -403,96 +755,133 @@ export default {
     })
     return d
   },
+  computed: {
+    labels () {
+      return {
+        title: this.$pgettext('Head/Settings/Title', 'Account Settings')
+      }
+    },
+    orderedSettingsFields () {
+      const self = this
+      return this.settings.order.map(id => {
+        return self.settings.fields[id]
+      })
+    },
+    settingsValues () {
+      const self = this
+      const s = {}
+      this.settings.order.forEach(setting => {
+        const conf = self.settings.fields[setting]
+        s[setting] = conf.value
+        if (setting === 'summary' && !conf.value.text) {
+          s[setting] = null
+        }
+      })
+      return s
+    }
+  },
   created () {
     this.fetchApps()
     this.fetchOwnedApps()
   },
-  mounted() {
-    $("select.dropdown").dropdown()
+  mounted () {
+    $('select.dropdown').dropdown()
   },
   methods: {
-    submitSettings() {
+    submitSettings () {
       this.settings.success = false
       this.settings.errors = []
-      let self = this
-      let payload = this.settingsValues
-      let url = `users/${this.$store.state.auth.username}/`
+      const self = this
+      const payload = this.settingsValues
+      const url = `users/${this.$store.state.auth.username}/`
       return axios.patch(url, payload).then(
         response => {
-          logger.default.info("Updated settings successfully")
+          logger.default.info('Updated settings successfully')
           self.settings.success = true
-          return axios.get("users/me/").then(response => {
-            self.$store.dispatch("auth/updateProfile", response.data)
+          return axios.get('users/me/').then(response => {
+            self.$store.dispatch('auth/updateProfile', response.data)
           })
         },
         error => {
-          logger.default.error("Error while updating settings")
+          logger.default.error('Error while updating settings')
           self.isLoading = false
           self.settings.errors = error.backendErrors
         }
       )
     },
-    fetchApps() {
+    fetchApps () {
       this.apps = []
-      let self = this
-      let url = `oauth/grants/`
+      const self = this
+      const url = 'oauth/grants/'
       return axios.get(url).then(
         response => {
           self.apps = response.data
         },
         error => {
+          logger.default.error('Error while fetching Apps')
+          self.isLoading = false
+          self.settings.errors = error.backendErrors
         }
       )
     },
-    fetchOwnedApps() {
+    fetchOwnedApps () {
       this.ownedApps = []
-      let self = this
-      let url = `oauth/apps/`
+      const self = this
+      const url = 'oauth/apps/'
       return axios.get(url).then(
         response => {
           self.ownedApps = response.data.results
         },
         error => {
+          logger.default.error('Error while fetching owned Apps')
+          self.isLoading = false
+          self.settings.errors = error.backendErrors
         }
       )
     },
     revokeApp (id) {
-      let self = this
-      let url = `oauth/grants/${id}/`
+      const self = this
+      const url = `oauth/grants/${id}/`
       return axios.delete(url).then(
         response => {
           self.apps = self.apps.filter(a => {
-            return a.client_id != id
+            return a.client_id !== id
           })
         },
         error => {
+          logger.default.error('Error while revoking App')
+          self.isLoading = false
+          self.settings.errors = error.backendErrors
         }
       )
     },
     deleteApp (id) {
-      let self = this
-      let url = `oauth/apps/${id}/`
+      const self = this
+      const url = `oauth/apps/${id}/`
       return axios.delete(url).then(
         response => {
           self.ownedApps = self.ownedApps.filter(a => {
-            return a.client_id != id
+            return a.client_id !== id
           })
         },
         error => {
+          logger.default.error('Error while deleting App')
+          self.isLoading = false
+          self.settings.errors = error.backendErrors
         }
       )
     },
-    submitAvatar(uuid) {
+    submitAvatar (uuid) {
       this.isLoadingAvatar = true
       this.avatarErrors = []
-      let self = this
+      const self = this
       axios
-        .patch(`users/${this.$store.state.auth.username}/`, {avatar: uuid})
+        .patch(`users/${this.$store.state.auth.username}/`, { avatar: uuid })
         .then(
           response => {
             this.isLoadingAvatar = false
             self.avatar = response.data.avatar
-            self.$store.commit("auth/avatar", response.data.avatar)
+            self.$store.commit('auth/avatar', response.data.avatar)
           },
           error => {
             self.isLoadingAvatar = false
@@ -500,21 +889,21 @@ export default {
           }
         )
     },
-    submitPassword() {
-      var self = this
+    submitPassword () {
+      const self = this
       self.isLoading = true
-      this.error = ""
-      var credentials = {
+      this.error = ''
+      const credentials = {
         old_password: this.old_password,
         new_password1: this.new_password,
         new_password2: this.new_password
       }
-      let url = "auth/registration/change-password/"
+      const url = 'auth/registration/change-password/'
       return axios.post(url, credentials).then(
         response => {
-          logger.default.info("Password successfully changed")
+          logger.default.info('Password successfully changed')
           self.$router.push({
-            name: "profile.overview",
+            name: 'profile.overview',
             params: {
               username: self.$store.state.auth.username
             }
@@ -522,27 +911,27 @@ export default {
         },
         error => {
           if (error.response.status === 400) {
-            self.passwordError = "invalid_credentials"
+            self.passwordError = 'invalid_credentials'
           } else {
-            self.passwordError = "unknown_error"
+            self.passwordError = 'unknown_error'
           }
           self.isLoading = false
         }
       )
     },
-    deleteAccount() {
+    deleteAccount () {
       this.isDeletingAccount = true
       this.accountDeleteErrors = []
-      let self = this
-      let payload = {
+      const self = this
+      const payload = {
         confirm: true,
-        password: this.password,
+        password: this.password
       }
-      axios.delete(`users/me/`, {data: payload})
+      axios.delete('users/me/', { data: payload })
         .then(
           response => {
             self.isDeletingAccount = false
-            let msg = self.$pgettext('*/Auth/Message', 'Your deletion request was submitted, your account and content will be deleted shortly')
+            const msg = self.$pgettext('*/Auth/Message', 'Your deletion request was submitted, your account and content will be deleted shortly')
             self.$store.commit('ui/addMessage', {
               content: msg,
               date: new Date()
@@ -556,21 +945,21 @@ export default {
         )
     },
 
-    changeEmail() {
+    changeEmail () {
       this.isChangingEmail = true
       this.changeEmailErrors = []
-      let self = this
-      let payload = {
+      const self = this
+      const payload = {
         password: this.emailPassword,
-        email: this.newEmail,
+        email: this.newEmail
       }
-      axios.post(`users/users/change-email/`, payload)
+      axios.post('users/users/change-email/', payload)
         .then(
           response => {
             self.isChangingEmail = false
             self.newEmail = null
             self.emailPassword = null
-            let msg = self.$pgettext('*/Auth/Message', 'Your e-mail address has been changed, please check your inbox for our confirmation message.')
+            const 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()
@@ -581,31 +970,6 @@ export default {
             self.changeEmailErrors = error.backendErrors
           }
         )
-    },
-  },
-  computed: {
-    labels() {
-      return {
-        title: this.$pgettext('Head/Settings/Title', "Account Settings")
-      }
-    },
-    orderedSettingsFields() {
-      let self = this
-      return this.settings.order.map(id => {
-        return self.settings.fields[id]
-      })
-    },
-    settingsValues() {
-      let self = this
-      let s = {}
-      this.settings.order.forEach(setting => {
-        let conf = self.settings.fields[setting]
-        s[setting] = conf.value
-        if (setting === 'summary' && !conf.value.text) {
-          s[setting] = null
-        }
-      })
-      return s
     }
   }
 }
diff --git a/front/src/components/common/UserMenu.vue b/front/src/components/common/UserMenu.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5110bafa9c1513f52507bf542d3549a08b93ceb6
--- /dev/null
+++ b/front/src/components/common/UserMenu.vue
@@ -0,0 +1,204 @@
+<template>
+  <div class="ui menu">
+    <div class="ui scrolling dropdown item">
+      <i class="language icon" />
+      {{ labels.language }}
+      <i class="dropdown icon" />
+      <div
+        id="language-select"
+        class="menu"
+      >
+        <a
+          v-for="(language, key) in $language.available"
+          :key="key"
+          :class="[{'active': $language.current === key},'item']"
+          :value="key"
+          @click="$store.dispatch('ui/currentLanguage', key)"
+        >{{ language }}</a>
+      </div>
+    </div>
+    <div class="ui dropdown item">
+      <i class="palette icon" />
+      {{ labels.theme }}
+      <i class="dropdown icon" />
+      <div
+        id="theme-select"
+        class="menu"
+      >
+        <a
+          v-for="theme in themes"
+          :key="theme.key"
+          :class="[{'active': $store.state.ui.theme === theme.key}, 'item']"
+          :value="theme.key"
+          @click="$store.dispatch('ui/theme', theme.key)"
+        >
+          <i :class="theme.icon" />
+          {{ theme.name }}
+        </a>
+      </div>
+    </div>
+    <template v-if="$store.state.auth.authenticated">
+      <div class="divider" />
+      <router-link
+        class="item"
+        :to="{name: 'profile.overview', params: { username: $store.state.auth.username },}"
+      >
+        <i class="user icon" />
+        {{ labels.profile }}
+      </router-link>
+      <router-link
+        v-if="$store.state.auth.authenticated"
+        class="item"
+        :to="{name: 'notifications'}"
+      >
+        <i class="bell icon" />
+        {{ labels.notifications }}
+      </router-link>
+      <router-link
+        class="item"
+        :to="{ path: '/settings' }"
+      >
+        <i class="cog icon" />
+        {{ labels.settings }}
+      </router-link>
+    </template>
+    <div class="divider" />
+    <div class="ui dropdown item">
+      <i class="life ring outline icon" />
+      {{ labels.support }}
+      <i class="dropdown icon" />
+      <div class="menu">
+        <a
+          href="https://forum.funkwhale.audio"
+          class="item"
+          target="_blank"
+        >
+          <i class="users icon" />
+          {{ labels.forum }}
+        </a>
+        <a
+          href="https://matrix.to/#/#funkwhale-troubleshooting:matrix.org"
+          class="item"
+          target="_blank"
+        >
+          <i class="comment icon" />
+          {{ labels.chat }}
+        </a>
+        <a
+          href="https://dev.funkwhale.audio/funkwhale/funkwhale/issues"
+          class="item"
+          target="_blank"
+        >
+          <i class="gitlab icon" />
+          {{ labels.git }}
+        </a>
+      </div>
+    </div>
+    <a
+      href="https://docs.funkwhale.audio"
+      class="item"
+      target="_blank"
+    >
+      <i class="book open icon" />
+      {{ labels.docs }}
+    </a>
+    <a
+      href=""
+      class="item"
+      @click.prevent="showShortcuts"
+    >
+      <i class="keyboard icon" />
+      {{ labels.shortcuts }}
+    </a>
+    <router-link
+      v-if="$route.path != '/about'"
+      class="item"
+      :to="{ name: 'about' }"
+    >
+      <i class="question circle outline icon" />
+      {{ labels.about }}
+    </router-link>
+    <template v-if="$store.state.auth.authenticated && $route.path != '/logout'">
+      <div class="divider" />
+      <router-link
+        class="item"
+        style="color: var(--danger-color)!important;"
+        :to="{ name: 'logout' }"
+      >
+        <i class="sign out alternate icon" />
+        {{ labels.logout }}
+      </router-link>
+    </template>
+    <template v-if="!$store.state.auth.authenticated">
+      <div class="divider" />
+      <router-link
+        class="item"
+        :to="{ name: 'login' }"
+      >
+        <i class="sign in alternate icon" />
+        {{ labels.login }}
+      </router-link>
+    </template>
+    <template v-if="!$store.state.auth.authenticated && $store.state.instance.settings.users.registration_enabled.value">
+      <router-link
+        class="item"
+        :to="{ name: 'signup' }"
+      >
+        <i class="user icon" />
+        {{ labels.signup }}
+      </router-link>
+    </template>
+  </div>
+</template>
+
+<script>
+
+import { mapGetters } from 'vuex'
+
+export default {
+  computed: {
+    labels () {
+      return {
+        profile: this.$pgettext('*/*/*/Noun', 'Profile'),
+        settings: this.$pgettext('*/*/*/Noun', 'Settings'),
+        logout: this.$pgettext('Sidebar/Login/List item.Link/Verb', 'Log out'),
+        about: this.$pgettext('Sidebar/About/List item.Link', 'About'),
+        shortcuts: this.$pgettext('*/*/*/Noun', 'Keyboard shortcuts'),
+        support: this.$pgettext('Sidebar/*/Listitem.Link', 'Help'),
+        forum: this.$pgettext('Sidebar/*/Listitem.Link', 'Forum'),
+        docs: this.$pgettext('Sidebar/*/Listitem.Link', 'Documentation'),
+        language: this.$pgettext('Footer/Settings/Dropdown.Label/Short, Verb', 'Change language'),
+        theme: this.$pgettext('Footer/Settings/Dropdown.Label/Short, Verb', 'Change theme'),
+        chat: this.$pgettext('Sidebar/*/Listitem.Link', 'Chat room'),
+        git: this.$pgettext('Footer/*/List item.Link', 'Issue tracker'),
+        login: this.$pgettext('*/*/Button.Label/Verb', 'Log in'),
+        signup: this.$pgettext('*/*/Button.Label/Verb', 'Sign up'),
+        notifications: this.$pgettext('*/Notifications/*', 'Notifications')
+      }
+    },
+    themes () {
+      return [
+        {
+          icon: 'sun icon',
+          name: this.$pgettext('Footer/Settings/Dropdown.Label/Theme name', 'Light'),
+          key: 'light'
+        },
+        {
+          icon: 'moon icon',
+          name: this.$pgettext('Footer/Settings/Dropdown.Label/Theme name', 'Dark'),
+          key: 'dark'
+        }
+      ]
+    },
+    ...mapGetters({
+      additionalNotifications: 'ui/additionalNotifications'
+    })
+  },
+  methods: {
+    showShortcuts () {
+      this.$emit('show:shortcuts-modal')
+      console.log(this.$store.getters['ui/windowSize'])
+    }
+  }
+}
+</script>
diff --git a/front/src/components/common/UserModal.vue b/front/src/components/common/UserModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7c0b27e4b390e557aed29e8f3c3efcc60754ca06
--- /dev/null
+++ b/front/src/components/common/UserModal.vue
@@ -0,0 +1,239 @@
+<template>
+  <!-- TODO make generic and move to semantic/modal? -->
+  <modal
+    :show="show"
+    :scrolling="true"
+    :fullscreen="false"
+    @update:show="$emit('update:show', $event)"
+  >
+    <div
+      v-if="$store.state.auth.authenticated"
+      class="header"
+    >
+      <img
+        v-if="$store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.medium_square_crop"
+        v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.medium_square_crop)"
+        alt=""
+        class="ui centered small circular image"
+      >
+      <actor-avatar
+        v-else
+        :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username,}"
+      />
+      <h3 class="user-modal title">
+        {{ labels.header }}
+      </h3>
+    </div>
+    <div
+      v-else
+      class="header"
+    >
+      <h3 class="ui center aligned icon header">
+        {{ labels.header }}
+      </h3>
+    </div>
+    <div class="content">
+      <div class="ui one column unstackable grid">
+        <div class="row">
+          <div
+            class="column"
+            role="button"
+            @click="[$emit('update:show', false), $emit('showLanguageModalEvent')]"
+          >
+            <i class="language icon user-modal list-icon" />
+            <span class="user-modal list-item">{{ labels.language }}:</span>
+            <div class="right floated">
+              <span class="user-modal list-item">{{ $language.available[$language.current] }}</span>
+              <i class="action-hint chevron right icon" />
+            </div>
+          </div>
+        </div>
+        <div class="row">
+          <div
+            class="column"
+            role="button"
+            @click="[$emit('update:show', false), $emit('showThemeModalEvent')]"
+          >
+            <i class="palette icon user-modal list-icon" />
+            <span class="user-modal list-item">{{ labels.theme }}:</span>
+            <div class="right floated">
+              <span class="user-modal list-item"> {{ themes.find(x => x.key ===$store.state.ui.theme).name }}</span>
+              <i class="action-hint chevron right icon user-modal" />
+            </div>
+          </div>
+        </div>
+        <div class="ui divider" />
+        <template v-if="$store.state.auth.authenticated">
+          <div class="row">
+            <div
+              class="column"
+              role="button"
+              @click.prevent.exact="$router.push({name: 'profile.overview', params: { username: $store.state.auth.username }})"
+            >
+              <i class="user icon user-modal list-icon" />
+              <span class="user-modal list-item">{{ labels.profile }}</span>
+            </div>
+          </div>
+          <div class="row">
+            <router-link
+              v-if="$store.state.auth.authenticated"
+              tag="div"
+              class="column"
+              :to="{name: 'notifications'}"
+              role="button"
+            >
+              <i class="user-modal list-icon bell icon" />
+              <span class="user-modal list-item">{{ labels.notifications }}</span>
+            </router-link>
+          </div>
+          <div class="row">
+            <router-link
+              tag="div"
+              class="column"
+              :to="{ path: '/settings' }"
+              role="button"
+            >
+              <i class="user-modal list-icon cog icon" />
+              <span class="user-modal list-item">{{ labels.settings }}</span>
+            </router-link>
+          </div>
+          <div class="ui divider" />
+        </template>
+        <div class="row">
+          <a
+            class="column"
+            href="https://docs.funkwhale.audio"
+            target="_blank"
+          >
+            <i class="user-modal list-icon book open icon" />
+            <span class="user-modal list-item">{{ labels.docs }}</span>
+          </a>
+        </div>
+        <div class="row">
+          <router-link
+            tag="div"
+            class="column"
+            :to="{ name: 'about' }"
+            role="button"
+          >
+            <i class="user-modal list-icon question circle outline icon" />
+            <span class="user-modal list-item">{{ labels.about }}</span>
+          </router-link>
+        </div>
+        <div class="ui divider" />
+        <template v-if="$store.state.auth.authenticated">
+          <router-link
+            tag="div"
+            class="column"
+            :to="{ name: 'logout' }"
+            role="button"
+          >
+            <i class="user-modal list-icon sign out alternate icon" />
+            <span class="user-modal list-item">{{ labels.logout }}</span>
+          </router-link>
+        </template>
+        <template v-if="!$store.state.auth.authenticated">
+          <router-link
+            tag="div"
+            class="column"
+            :to="{ name: 'login' }"
+            role="button"
+          >
+            <i class="user-modal list-icon sign in alternate icon" />
+            <span class="user-modal list-item">{{ labels.login }}</span>
+          </router-link>
+        </template>
+        <template
+          v-if="!$store.state.auth.authenticated"
+          &&
+          $store.state.instance.settings.users.registration_enabled.value
+        >
+          <router-link
+            tag="div"
+            class="column"
+            :to="{ name: 'signup' }"
+            role="button"
+          >
+            <i class="user-modal list-item user icon" />
+            <span class="user-modal list-item">{{ labels.signup }}</span>
+          </router-link>
+        </template>
+      </div>
+    </div>
+  </modal>
+</template>
+
+<script>
+import Modal from '@/components/semantic/Modal'
+import { mapGetters } from 'vuex'
+
+export default {
+  components: {
+    Modal
+  },
+  props: {
+    show: { type: Boolean, required: true }
+  },
+  computed: {
+    labels () {
+      return {
+        header: this.$pgettext('Popup/Title/Noun', 'Options'),
+        profile: this.$pgettext('*/*/*/Noun', 'Profile'),
+        settings: this.$pgettext('*/*/*/Noun', 'Settings'),
+        logout: this.$pgettext('Sidebar/Login/List item.Link/Verb', 'Log out'),
+        about: this.$pgettext('Sidebar/About/List item.Link', 'About'),
+        shortcuts: this.$pgettext('*/*/*/Noun', 'Keyboard shortcuts'),
+        support: this.$pgettext('Sidebar/*/Listitem.Link', 'Help'),
+        forum: this.$pgettext('Sidebar/*/Listitem.Link', 'Forum'),
+        docs: this.$pgettext('Sidebar/*/Listitem.Link', 'Documentation'),
+        language: this.$pgettext(
+          'Sidebar/Settings/Dropdown.Label/Short, Verb',
+          'Language'
+        ),
+        theme: this.$pgettext(
+          'Sidebar/Settings/Dropdown.Label/Short, Verb',
+          'Theme'
+        ),
+        chat: this.$pgettext('Sidebar/*/Listitem.Link', 'Chat room'),
+        git: this.$pgettext('Sidebar/*/List item.Link', 'Issue tracker'),
+        login: this.$pgettext('*/*/Button.Label/Verb', 'Log in'),
+        signup: this.$pgettext('*/*/Button.Label/Verb', 'Sign up'),
+        notifications: this.$pgettext('*/Notifications/*', 'Notifications'),
+        useOtherInstance: this.$pgettext(
+          'Sidebar/*/List item.Link',
+          'Use another instance'
+        )
+      }
+    },
+    themes () {
+      return [
+        {
+          icon: 'sun icon',
+          name: this.$pgettext(
+            'Footer/Settings/Dropdown.Label/Theme name',
+            'Light'
+          ),
+          key: 'light'
+        },
+        {
+          icon: 'moon icon',
+          name: this.$pgettext(
+            'Footer/Settings/Dropdown.Label/Theme name',
+            'Dark'
+          ),
+          key: 'dark'
+        }
+      ]
+    },
+    ...mapGetters({
+      additionalNotifications: 'ui/additionalNotifications'
+    })
+  }
+}
+</script>
+
+<style>
+.action-hint {
+  margin-left: 1rem !important;
+}
+</style>
diff --git a/front/src/components/semantic/Modal.vue b/front/src/components/semantic/Modal.vue
index 223c0e730ae9816d1ee8c34d48707d2f6c1cd99f..a40511f642f06ed71dff77844994b444ad405f5d 100644
--- a/front/src/components/semantic/Modal.vue
+++ b/front/src/components/semantic/Modal.vue
@@ -1,9 +1,10 @@
 <template>
   <div :class="additionalClasses.concat(['ui', {'active': show}, {'scrolling': scrolling} ,{'overlay fullscreen': fullscreen && ['phone', 'tablet'].indexOf($store.getters['ui/windowSize']) > -1},'modal'])">
-    <i tabindex=0 class="close inside icon"></i>
-    <slot v-if="show">
-
-    </slot>
+    <i
+      tabindex="0"
+      class="close inside icon"
+    />
+    <slot v-if="show" />
   </div>
 </template>
 
@@ -13,15 +14,41 @@ import createFocusTrap from 'focus-trap'
 
 export default {
   props: {
-    show: {type: Boolean, required: true},
-    fullscreen: {type: Boolean, default: true},
-    scrolling: {type: Boolean, required: false, default: false},
-    additionalClasses: {type: Array, required: false, default: () => []}
+    show: { type: Boolean, required: true },
+    fullscreen: { type: Boolean, default: true },
+    scrolling: { type: Boolean, required: false, default: false },
+    additionalClasses: { type: Array, required: false, default: () => [] }
   },
   data () {
     return {
       control: null,
-      focusTrap: null,
+      focusTrap: null
+    }
+  },
+  watch: {
+    show: {
+      handler (newValue) {
+        if (newValue) {
+          this.initModal()
+          this.$emit('show')
+          this.control.modal('show')
+          this.focusTrap.activate()
+          this.focusTrap.unpause()
+          document.body.classList.add('scrolling')
+        } else {
+          if (this.control) {
+            this.$emit('hide')
+            this.control.modal('hide')
+            this.control.remove()
+            this.focusTrap.deactivate()
+            this.focusTrap.pause()
+            document.body.classList.remove('scrolling')
+          }
+        }
+      }
+    },
+    $route (to, from) {
+      this.closeModal()
     }
   },
   mounted () {
@@ -52,29 +79,9 @@ export default {
           this.focusTrap.unpause()
         }.bind(this)
       })
-    }
-  },
-  watch: {
-    show: {
-      handler (newValue) {
-        if (newValue) {
-          this.initModal()
-          this.$emit('show')
-          this.control.modal('show')
-          this.focusTrap.activate()
-          this.focusTrap.unpause()
-          document.body.classList.add('scrolling')
-        } else {
-          if (this.control) {
-            this.$emit('hide')
-            this.control.modal('hide')
-            this.control.remove()
-            this.focusTrap.deactivate()
-            this.focusTrap.pause()
-            document.body.classList.remove('scrolling')
-          }
-        }
-      }
+    },
+    closeModal () {
+      this.$emit('update:show', false)
     }
   }
 
diff --git a/front/src/router/index.js b/front/src/router/index.js
index 35ce8be5d73e3c5c7e942ea131c39533bd44efdd..210f0aa43859e13fd56c2fb71eeb6db0df442d81 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -224,7 +224,7 @@ export default new Router({
               )
           },
           {
-            path: 'activity',
+            path: '/activity',
             name: `profile${route.suffix}.activity`,
             component: () =>
               import(
@@ -318,7 +318,7 @@ export default new Router({
         import(/* webpackChunkName: "admin" */ '@/views/admin/library/Base'),
       children: [
         {
-          path: 'edits',
+          path: '/edits',
           name: 'manage.library.edits',
           component: () =>
             import(
@@ -331,7 +331,7 @@ export default new Router({
           }
         },
         {
-          path: 'artists',
+          path: '/artists',
           name: 'manage.library.artists',
           component: () =>
             import(
@@ -344,7 +344,7 @@ export default new Router({
           }
         },
         {
-          path: 'artists/:id',
+          path: '/artists/:id',
           name: 'manage.library.artists.detail',
           component: () =>
             import(
@@ -353,7 +353,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'channels',
+          path: '/channels',
           name: 'manage.channels',
           component: () =>
             import(
@@ -366,7 +366,7 @@ export default new Router({
           }
         },
         {
-          path: 'channels/:id',
+          path: '/channels/:id',
           name: 'manage.channels.detail',
           component: () =>
             import(
@@ -375,7 +375,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'albums',
+          path: '/albums',
           name: 'manage.library.albums',
           component: () =>
             import(
@@ -388,7 +388,7 @@ export default new Router({
           }
         },
         {
-          path: 'albums/:id',
+          path: '/albums/:id',
           name: 'manage.library.albums.detail',
           component: () =>
             import(
@@ -397,7 +397,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'tracks',
+          path: '/tracks',
           name: 'manage.library.tracks',
           component: () =>
             import(
@@ -410,7 +410,7 @@ export default new Router({
           }
         },
         {
-          path: 'tracks/:id',
+          path: '/tracks/:id',
           name: 'manage.library.tracks.detail',
           component: () =>
             import(
@@ -419,7 +419,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'libraries',
+          path: '/libraries',
           name: 'manage.library.libraries',
           component: () =>
             import(
@@ -432,7 +432,7 @@ export default new Router({
           }
         },
         {
-          path: 'libraries/:id',
+          path: '/libraries/:id',
           name: 'manage.library.libraries.detail',
           component: () =>
             import(
@@ -441,7 +441,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'uploads',
+          path: '/uploads',
           name: 'manage.library.uploads',
           component: () =>
             import(
@@ -454,7 +454,7 @@ export default new Router({
           }
         },
         {
-          path: 'uploads/:id',
+          path: '/uploads/:id',
           name: 'manage.library.uploads.detail',
           component: () =>
             import(
@@ -463,7 +463,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'tags',
+          path: '/tags',
           name: 'manage.library.tags',
           component: () =>
             import(
@@ -476,7 +476,7 @@ export default new Router({
           }
         },
         {
-          path: 'tags/:id',
+          path: '/tags/:id',
           name: 'manage.library.tags.detail',
           component: () =>
             import(
@@ -493,7 +493,7 @@ export default new Router({
         import(/* webpackChunkName: "admin" */ '@/views/admin/users/Base'),
       children: [
         {
-          path: 'users',
+          path: '/users',
           name: 'manage.users.users.list',
           component: () =>
             import(
@@ -501,7 +501,7 @@ export default new Router({
             )
         },
         {
-          path: 'invitations',
+          path: '/invitations',
           name: 'manage.users.invitations.list',
           component: () =>
             import(
@@ -517,7 +517,7 @@ export default new Router({
         import(/* webpackChunkName: "admin" */ '@/views/admin/moderation/Base'),
       children: [
         {
-          path: 'domains',
+          path: '/domains',
           name: 'manage.moderation.domains.list',
           component: () =>
             import(
@@ -525,7 +525,7 @@ export default new Router({
             )
         },
         {
-          path: 'domains/:id',
+          path: '/domains/:id',
           name: 'manage.moderation.domains.detail',
           component: () =>
             import(
@@ -534,7 +534,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'accounts',
+          path: '/accounts',
           name: 'manage.moderation.accounts.list',
           component: () =>
             import(
@@ -547,7 +547,7 @@ export default new Router({
           }
         },
         {
-          path: 'accounts/:id',
+          path: '/accounts/:id',
           name: 'manage.moderation.accounts.detail',
           component: () =>
             import(
@@ -556,7 +556,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'reports',
+          path: '/reports',
           name: 'manage.moderation.reports.list',
           component: () =>
             import(
@@ -570,7 +570,7 @@ export default new Router({
           }
         },
         {
-          path: 'reports/:id',
+          path: '/reports/:id',
           name: 'manage.moderation.reports.detail',
           component: () =>
             import(
@@ -579,7 +579,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'requests',
+          path: '/requests',
           name: 'manage.moderation.requests.list',
           component: () =>
             import(
@@ -593,7 +593,7 @@ export default new Router({
           }
         },
         {
-          path: 'requests/:id',
+          path: '/requests/:id',
           name: 'manage.moderation.requests.detail',
           component: () =>
             import(
@@ -609,13 +609,13 @@ export default new Router({
         import(/* webpackChunkName: "core" */ '@/components/library/Library'),
       children: [
         {
-          path: '',
+          path: '/',
           component: () =>
             import(/* webpackChunkName: "core" */ '@/components/library/Home'),
           name: 'library.index'
         },
         {
-          path: 'me',
+          path: '/me',
           component: () =>
             import(/* webpackChunkName: "core" */ '@/components/library/Home'),
           name: 'library.me',
@@ -624,7 +624,7 @@ export default new Router({
           })
         },
         {
-          path: 'artists/',
+          path: '/artists/',
           name: 'library.artists.browse',
           component: () =>
             import(
@@ -641,7 +641,7 @@ export default new Router({
           })
         },
         {
-          path: 'me/artists',
+          path: '/me/artists',
           name: 'library.artists.me',
           component: () =>
             import(
@@ -659,7 +659,7 @@ export default new Router({
           })
         },
         {
-          path: 'albums/',
+          path: '/albums/',
           name: 'library.albums.browse',
           component: () =>
             import(
@@ -676,7 +676,7 @@ export default new Router({
           })
         },
         {
-          path: 'podcasts/',
+          path: '/podcasts/',
           name: 'library.podcasts.browse',
           component: () =>
             import(
@@ -693,7 +693,7 @@ export default new Router({
           })
         },
         {
-          path: 'me/albums',
+          path: '/me/albums',
           name: 'library.albums.me',
           component: () =>
             import(
@@ -711,7 +711,7 @@ export default new Router({
           })
         },
         {
-          path: 'radios/',
+          path: '/radios/',
           name: 'library.radios.browse',
           component: () =>
             import(
@@ -725,7 +725,7 @@ export default new Router({
           })
         },
         {
-          path: 'me/radios/',
+          path: '/me/radios/',
           name: 'library.radios.me',
           component: () =>
             import(
@@ -740,7 +740,7 @@ export default new Router({
           })
         },
         {
-          path: 'radios/build',
+          path: '/radios/build',
           name: 'library.radios.build',
           component: () =>
             import(
@@ -749,7 +749,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'radios/build/:id',
+          path: '/radios/build/:id',
           name: 'library.radios.edit',
           component: () =>
             import(
@@ -758,14 +758,14 @@ export default new Router({
           props: true
         },
         {
-          path: 'radios/:id',
+          path: '/radios/:id',
           name: 'library.radios.detail',
           component: () =>
             import(/* webpackChunkName: "radios" */ '@/views/radios/Detail'),
           props: true
         },
         {
-          path: 'playlists/',
+          path: '/playlists/',
           name: 'library.playlists.browse',
           component: () =>
             import(/* webpackChunkName: "playlists" */ '@/views/playlists/List'),
@@ -777,7 +777,7 @@ export default new Router({
           })
         },
         {
-          path: 'me/playlists/',
+          path: '/me/playlists/',
           name: 'library.playlists.me',
           component: () =>
             import(/* webpackChunkName: "playlists" */ '@/views/playlists/List'),
@@ -790,7 +790,7 @@ export default new Router({
           })
         },
         {
-          path: 'playlists/:id',
+          path: '/playlists/:id',
           name: 'library.playlists.detail',
           component: () =>
             import(/* webpackChunkName: "playlists" */ '@/views/playlists/Detail'),
@@ -800,7 +800,7 @@ export default new Router({
           })
         },
         {
-          path: 'tags/:id',
+          path: '/tags/:id',
           name: 'library.tags.detail',
           component: () =>
             import(
@@ -809,7 +809,7 @@ export default new Router({
           props: true
         },
         {
-          path: 'artists/:id',
+          path: '/artists/:id',
           component: () =>
             import(
               /* webpackChunkName: "artists" */ '@/components/library/ArtistBase'
@@ -817,7 +817,7 @@ export default new Router({
           props: true,
           children: [
             {
-              path: '',
+              path: '/',
               name: 'library.artists.detail',
               component: () =>
                 import(
@@ -825,7 +825,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit',
+              path: '/edit',
               name: 'library.artists.edit',
               component: () =>
                 import(
@@ -833,7 +833,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit/:editId',
+              path: '/edit/:editId',
               name: 'library.artists.edit.detail',
               component: () =>
                 import(
@@ -844,7 +844,7 @@ export default new Router({
           ]
         },
         {
-          path: 'albums/:id',
+          path: '/albums/:id',
           component: () =>
             import(
               /* webpackChunkName: "albums" */ '@/components/library/AlbumBase'
@@ -852,7 +852,7 @@ export default new Router({
           props: true,
           children: [
             {
-              path: '',
+              path: '/',
               name: 'library.albums.detail',
               component: () =>
                 import(
@@ -860,7 +860,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit',
+              path: '/edit',
               name: 'library.albums.edit',
               component: () =>
                 import(
@@ -868,7 +868,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit/:editId',
+              path: '/edit/:editId',
               name: 'library.albums.edit.detail',
               component: () =>
                 import(
@@ -879,7 +879,7 @@ export default new Router({
           ]
         },
         {
-          path: 'tracks/:id',
+          path: '/tracks/:id',
           component: () =>
             import(
               /* webpackChunkName: "tracks" */ '@/components/library/TrackBase'
@@ -887,7 +887,7 @@ export default new Router({
           props: true,
           children: [
             {
-              path: '',
+              path: '/',
               name: 'library.tracks.detail',
               component: () =>
                 import(
@@ -895,7 +895,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit',
+              path: '/edit',
               name: 'library.tracks.edit',
               component: () =>
                 import(
@@ -903,7 +903,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit/:editId',
+              path: '/edit/:editId',
               name: 'library.tracks.edit.detail',
               component: () =>
                 import(
@@ -914,7 +914,7 @@ export default new Router({
           ]
         },
         {
-          path: 'uploads/:id',
+          path: '/uploads/:id',
           name: 'library.uploads.detail',
           props: true,
           component: () =>
@@ -924,7 +924,7 @@ export default new Router({
         },
         {
           // browse a single library via it's uuid
-          path: ':id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})',
+          path: '/:id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})',
           props: true,
           component: () =>
             import(
@@ -932,7 +932,7 @@ export default new Router({
             ),
           children: [
             {
-              path: '',
+              path: '/',
               name: 'library.detail',
               component: () =>
                 import(
@@ -940,7 +940,7 @@ export default new Router({
                 )
             },
             {
-              path: 'albums',
+              path: '/albums',
               name: 'library.detail.albums',
               component: () =>
                 import(
@@ -948,7 +948,7 @@ export default new Router({
                 )
             },
             {
-              path: 'tracks',
+              path: '/tracks',
               name: 'library.detail.tracks',
               component: () =>
                 import(
@@ -956,7 +956,7 @@ export default new Router({
                 )
             },
             {
-              path: 'edit',
+              path: '/edit',
               name: 'library.detail.edit',
               component: () =>
                 import(
@@ -964,7 +964,7 @@ export default new Router({
                 )
             },
             {
-              path: 'upload',
+              path: '/upload',
               name: 'library.detail.upload',
               component: () =>
                 import(
@@ -995,7 +995,7 @@ export default new Router({
         ),
       children: [
         {
-          path: '',
+          path: '/',
           name: 'channels.detail',
           component: () =>
             import(
@@ -1003,7 +1003,7 @@ export default new Router({
             )
         },
         {
-          path: 'episodes',
+          path: '/episodes',
           name: 'channels.detail.episodes',
           component: () =>
             import(
diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss
index b867d27413df69ee0c081f1f761e832ce61ba395..217ee30cee90fb7ba87982adfd0796498b8e3011 100644
--- a/front/src/style/_main.scss
+++ b/front/src/style/_main.scss
@@ -48,6 +48,7 @@ $bottom-player-height: 4rem;
 @import "./components/_track_widget.scss";
 @import "./components/_track_table.scss";
 @import "./components/_user_link.scss";
+@import "./components/user_modal.scss";
 @import "./components/_volume_control.scss";
 @import "./components/_loaders.scss";
 
diff --git a/front/src/style/components/_sidebar.scss b/front/src/style/components/_sidebar.scss
index 99867533ea8f54338484596a503488865bfca010..ada61d833e6b647f4711a1e6c6f512912317f03e 100644
--- a/front/src/style/components/_sidebar.scss
+++ b/front/src/style/components/_sidebar.scss
@@ -214,6 +214,10 @@
             }
         }
     }
+    .ui.user-dropdown .ui.menu {
+        left: auto;
+        right: 0;
+    }
     .ui.user-dropdown>.text>.label {
         margin-right: 0;
     }
@@ -234,4 +238,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/front/src/style/components/_user_modal.scss b/front/src/style/components/_user_modal.scss
new file mode 100644
index 0000000000000000000000000000000000000000..9fc87aa0ea49cc90c1b7eb1602e094424aa391ce
--- /dev/null
+++ b/front/src/style/components/_user_modal.scss
@@ -0,0 +1,31 @@
+.ui.overlay.fullscreen.modal {
+  .user-modal-title,
+  .user-modal-subtitle {
+    margin: 0.1em;
+  }
+  .user-modal-subtitle {
+    font-weight: normal;
+  }
+  .user-modal.list-icon {
+    margin-right: 1em;
+  }
+  .user-modal.list-item {
+    font-weight: bold;
+    font-size: large;
+  }
+  a {
+    color: var(--text-color);
+    text-decoration: none ;
+  }
+}
+
+.scrolling.dimmable.dimmed {
+  > .dimmer {
+    overflow: auto;
+    --webkit-overflow-scrolling: touch;
+  }
+  ::-webkit-scrollbar {
+    width: 0px;
+    background: transparent;
+  }
+}
\ No newline at end of file