From c0f98eeafe5eeb5a1ffed74c3783581b5cd66aea Mon Sep 17 00:00:00 2001
From: absidue <48293849+absidue@users.noreply.github.com>
Date: Thu, 13 Oct 2022 13:51:15 +0200
Subject: [PATCH] Use named parameters instead of $ and % in localised strings
 (#2685)
* Use named parameters instead of $ and % in localised strings
* Fix URL warning again
* Update placeholders in most locales
* Let the translators fix the problematic RTL strings
* Fix the missing quotes in some of the YAML files
---
 src/renderer/App.js                           |   6 +-
 .../components/data-settings/data-settings.js |   2 +-
 .../external-player-settings.js               |   4 +-
 .../ft-age-restricted/ft-age-restricted.js    |   2 +-
 .../ft-list-playlist/ft-list-playlist.js      |   1 -
 .../ft-list-playlist/ft-list-playlist.vue     |   2 +-
 .../components/ft-list-video/ft-list-video.js |   5 -
 .../ft-list-video/ft-list-video.vue           |   2 +-
 .../ft-profile-channel-list.js                |   3 +-
 .../ft-profile-edit/ft-profile-edit.js        |   4 +-
 .../ft-profile-filter-channels-list.js        |   3 +-
 .../ft-profile-selector.js                    |   2 +-
 .../ft-video-player/ft-video-player.js        |   8 +-
 .../general-settings/general-settings.js      |   3 +-
 .../general-settings/general-settings.vue     |   2 +-
 .../watch-video-comments.js                   |   4 -
 .../watch-video-comments.vue                  |   2 +-
 .../watch-video-info/watch-video-info.js      |   4 +-
 .../watch-video-info/watch-video-info.vue     |   2 +-
 src/renderer/i18n/index.js                    |   4 +-
 src/renderer/store/modules/utils.js           | 112 +++++++-----------
 src/renderer/views/Channel/Channel.js         |   3 +-
 .../SubscribedChannels/SubscribedChannels.js  |   4 +-
 .../SubscribedChannels/SubscribedChannels.vue |   4 +-
 static/locales/ar.yaml                        |  61 +++++-----
 static/locales/as.yaml                        |   1 -
 static/locales/az.yaml                        |  31 ++---
 static/locales/bg.yaml                        |  57 +++++----
 static/locales/bn.yaml                        |  15 ++-
 static/locales/bs.yaml                        |  15 ++-
 static/locales/ca.yaml                        |  33 +++---
 static/locales/cs.yaml                        |  55 +++++----
 static/locales/da.yaml                        |  57 +++++----
 static/locales/de-DE.yaml                     |  54 ++++-----
 static/locales/el.yaml                        |  45 ++++---
 static/locales/en-US.yaml                     |  60 ++++------
 static/locales/en_GB.yaml                     |  60 ++++------
 static/locales/eo.yaml                        |  15 ++-
 static/locales/es-MX.yaml                     |  47 ++++----
 static/locales/es.yaml                        |  59 +++++----
 static/locales/es_AR.yaml                     |  15 ++-
 static/locales/et.yaml                        |  55 +++++----
 static/locales/eu.yaml                        |  57 +++++----
 static/locales/fa.yaml                        |  28 ++---
 static/locales/fi.yaml                        |  53 ++++-----
 static/locales/fil.yaml                       |   7 +-
 static/locales/fr-FR.yaml                     |  57 +++++----
 static/locales/gl.yaml                        |  29 +++--
 static/locales/gsw.yaml                       |  32 ++---
 static/locales/he.yaml                        |  55 ++++-----
 static/locales/hi.yaml                        |  15 ++-
 static/locales/hr.yaml                        |  57 +++++----
 static/locales/hu.yaml                        |  57 +++++----
 static/locales/id.yaml                        |  45 ++++---
 static/locales/is.yaml                        |  59 +++++----
 static/locales/it.yaml                        |  57 +++++----
 static/locales/ja.yaml                        |  51 ++++----
 static/locales/ka.yaml                        |  36 +++---
 static/locales/km.yaml                        |  27 ++---
 static/locales/ko.yaml                        |  51 ++++----
 static/locales/ku.yaml                        |  15 ++-
 static/locales/la.yaml                        |  17 ++-
 static/locales/lt.yaml                        |  40 +++----
 static/locales/nb_NO.yaml                     |  43 ++++---
 static/locales/ne.yaml                        |  24 ++--
 static/locales/nl.yaml                        |  57 +++++----
 static/locales/nn.yaml                        |  29 +++--
 static/locales/pl.yaml                        |  55 +++++----
 static/locales/pt-BR.yaml                     |  57 +++++----
 static/locales/pt-PT.yaml                     |  53 ++++-----
 static/locales/pt.yaml                        |  53 ++++-----
 static/locales/ro.yaml                        |  57 +++++----
 static/locales/ru.yaml                        |  55 +++++----
 static/locales/sat.yaml                       |  15 ++-
 static/locales/si.yaml                        |  15 ++-
 static/locales/sk.yaml                        |  37 +++---
 static/locales/sl.yaml                        |  19 ++-
 static/locales/sr.yaml                        |  17 ++-
 static/locales/sv.yaml                        |  39 +++---
 static/locales/tok.yaml                       |  27 ++---
 static/locales/tr.yaml                        |  51 ++++----
 static/locales/uk.yaml                        |  55 +++++----
 static/locales/ur.yaml                        |  51 ++++----
 static/locales/vi.yaml                        |  57 +++++----
 static/locales/zh-CN.yaml                     |  51 ++++----
 static/locales/zh-TW.yaml                     |  51 ++++----
 86 files changed, 1279 insertions(+), 1457 deletions(-)
diff --git a/src/renderer/App.js b/src/renderer/App.js
index f7447b94..ec94e628 100644
--- a/src/renderer/App.js
+++ b/src/renderer/App.js
@@ -207,8 +207,7 @@ export default Vue.extend({
             this.updateChangelog = marked.parse(json[0].body)
             this.changeLogTitle = json[0].name
 
-            const message = this.$t('Version $ is now available!  Click for more details')
-            this.updateBannerMessage = message.replace('$', versionNumber)
+            this.updateBannerMessage = this.$t('Version {versionNumber} is now available!  Click for more details', { versionNumber })
 
             const appVersion = packageDetails.version.split('.')
             const latestVersion = versionNumber.split('.')
@@ -242,8 +241,7 @@ export default Vue.extend({
           const latestPubDate = new Date(latestBlog.pubDate)
 
           if (lastAppWasRunning === null || latestPubDate > lastAppWasRunning) {
-            const message = this.$t('A new blog is now available, $. Click to view more')
-            this.blogBannerMessage = message.replace('$', latestBlog.title)
+            this.blogBannerMessage = this.$t('A new blog is now available, {blogTitle}. Click to view more', { blogTitle: latestBlog.title })
             this.latestBlogUrl = latestBlog.link
             this.showBlogBanner = true
           }
diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js
index 14201469..2f28ab5d 100644
--- a/src/renderer/components/data-settings/data-settings.js
+++ b/src/renderer/components/data-settings/data-settings.js
@@ -1013,7 +1013,7 @@ export default Vue.extend({
         const objectKeys = Object.keys(playlistObject)
 
         if ((objectKeys.length < requiredKeys.length) || playlistObject.videos.length === 0) {
-          const message = this.$t('Settings.Data Settings.Playlist insufficient data').replace('$', playlistData.playlistName)
+          const message = this.$t('Settings.Data Settings.Playlist insufficient data', { playlist: playlistData.playlistName })
           this.showToast({
             message: message
           })
diff --git a/src/renderer/components/external-player-settings/external-player-settings.js b/src/renderer/components/external-player-settings/external-player-settings.js
index 1b5eabb4..5e4c6841 100644
--- a/src/renderer/components/external-player-settings/external-player-settings.js
+++ b/src/renderer/components/external-player-settings/external-player-settings.js
@@ -47,8 +47,8 @@ export default Vue.extend({
 
       const cmdArgs = this.$store.getters.getExternalPlayerCmdArguments[this.externalPlayer]
       if (cmdArgs && typeof cmdArgs.defaultCustomArguments === 'string' && cmdArgs.defaultCustomArguments !== '') {
-        const defaultArgs = this.$t('Tooltips.External Player Settings.DefaultCustomArgumentsTemplate')
-          .replace('$', cmdArgs.defaultCustomArguments)
+        const defaultArgs = this.$t('Tooltips.External Player Settings.DefaultCustomArgumentsTemplate',
+          { defaultCustomArguments: cmdArgs.defaultCustomArguments })
         return `${tooltip} ${defaultArgs}`
       }
 
diff --git a/src/renderer/components/ft-age-restricted/ft-age-restricted.js b/src/renderer/components/ft-age-restricted/ft-age-restricted.js
index 5769012b..cc38888f 100644
--- a/src/renderer/components/ft-age-restricted/ft-age-restricted.js
+++ b/src/renderer/components/ft-age-restricted/ft-age-restricted.js
@@ -16,7 +16,7 @@ export default Vue.extend({
 
     restrictedMessage: function () {
       const contentType = this.$t('Age Restricted.Type.' + this.contentTypeString)
-      return this.$t('Age Restricted.This $contentType is age restricted').replace('$contentType', contentType)
+      return this.$t('Age Restricted.This {videoOrPlaylist} is age restricted', { videoOrPlaylist: contentType })
     }
   }
 })
diff --git a/src/renderer/components/ft-list-playlist/ft-list-playlist.js b/src/renderer/components/ft-list-playlist/ft-list-playlist.js
index d6305b9e..308ec463 100644
--- a/src/renderer/components/ft-list-playlist/ft-list-playlist.js
+++ b/src/renderer/components/ft-list-playlist/ft-list-playlist.js
@@ -65,7 +65,6 @@ export default Vue.extend({
   methods: {
     handleExternalPlayer: function () {
       this.openInExternalPlayer({
-        strings: this.$t('Video.External Player'),
         watchProgress: 0,
         playbackRate: this.defaultPlayback,
         videoId: null,
diff --git a/src/renderer/components/ft-list-playlist/ft-list-playlist.vue b/src/renderer/components/ft-list-playlist/ft-list-playlist.vue
index 6e055c11..e7f38432 100644
--- a/src/renderer/components/ft-list-playlist/ft-list-playlist.vue
+++ b/src/renderer/components/ft-list-playlist/ft-list-playlist.vue
@@ -25,7 +25,7 @@
     
       
        profile.name !== this.profile.name ? [profile.name] : [])
     },
     selectedText: function () {
-      const localeText = this.$t('Profile.$ selected')
-      return localeText.replace('$', this.selectedLength)
+      return this.$t('Profile.{number} selected', { number: this.selectedLength })
     }
   },
   watch: {
diff --git a/src/renderer/components/ft-profile-selector/ft-profile-selector.js b/src/renderer/components/ft-profile-selector/ft-profile-selector.js
index c1507a78..94d343b9 100644
--- a/src/renderer/components/ft-profile-selector/ft-profile-selector.js
+++ b/src/renderer/components/ft-profile-selector/ft-profile-selector.js
@@ -78,7 +78,7 @@ export default Vue.extend({
         if (targetProfile) {
           this.updateActiveProfile(targetProfile._id)
 
-          const message = this.$t('Profile.$ is now the active profile').replace('$', profile.name)
+          const message = this.$t('Profile.{profile} is now the active profile', { profile: profile.name })
           this.showToast({ message })
         }
       }
diff --git a/src/renderer/components/ft-video-player/ft-video-player.js b/src/renderer/components/ft-video-player/ft-video-player.js
index 1a1b2836..bb5dd269 100644
--- a/src/renderer/components/ft-video-player/ft-video-player.js
+++ b/src/renderer/components/ft-video-player/ft-video-player.js
@@ -1344,7 +1344,7 @@ export default Vue.extend({
       } catch (err) {
         console.error(`Parse failed: ${err.message}`)
         this.showToast({
-          message: this.$t('Screenshot Error').replace('$', err.message)
+          message: this.$t('Screenshot Error', { error: err.message })
         })
         canvas.remove()
         return
@@ -1412,7 +1412,7 @@ export default Vue.extend({
           } catch (err) {
             console.error(err)
             this.showToast({
-              message: this.$t('Screenshot Error').replace('$', err)
+              message: this.$t('Screenshot Error', { error: err })
             })
             canvas.remove()
             return
@@ -1429,11 +1429,11 @@ export default Vue.extend({
             if (err) {
               console.error(err)
               this.showToast({
-                message: this.$t('Screenshot Error').replace('$', err)
+                message: this.$t('Screenshot Error', { error: err })
               })
             } else {
               this.showToast({
-                message: this.$t('Screenshot Success').replace('$', filePath)
+                message: this.$t('Screenshot Success', { filePath })
               })
             }
           })
diff --git a/src/renderer/components/general-settings/general-settings.js b/src/renderer/components/general-settings/general-settings.js
index 6d650a48..f01a9332 100644
--- a/src/renderer/components/general-settings/general-settings.js
+++ b/src/renderer/components/general-settings/general-settings.js
@@ -193,9 +193,8 @@ export default Vue.extend({
       const instance = this.currentInvidiousInstance
       this.updateDefaultInvidiousInstance(instance)
 
-      const message = this.$t('Default Invidious instance has been set to $')
       this.showToast({
-        message: message.replace('$', instance)
+        message: this.$t('Default Invidious instance has been set to {instance}', { instance })
       })
     },
 
diff --git a/src/renderer/components/general-settings/general-settings.vue b/src/renderer/components/general-settings/general-settings.vue
index a5aff4a1..82f25f6b 100644
--- a/src/renderer/components/general-settings/general-settings.vue
+++ b/src/renderer/components/general-settings/general-settings.vue
@@ -116,7 +116,7 @@
         v-if="defaultInvidiousInstance !== ''"
         class="center"
       >
-        {{ $t('Settings.General Settings.The currently set default instance is $').replace('$', defaultInvidiousInstance) }}
+        {{ $t('Settings.General Settings.The currently set default instance is {instance}', { instance: defaultInvidiousInstance }) }}
       
       
         
diff --git a/src/renderer/components/watch-video-comments/watch-video-comments.js b/src/renderer/components/watch-video-comments/watch-video-comments.js
index 482b66db..db49725d 100644
--- a/src/renderer/components/watch-video-comments/watch-video-comments.js
+++ b/src/renderer/components/watch-video-comments/watch-video-comments.js
@@ -249,10 +249,6 @@ export default Vue.extend({
         comment.dataType = 'local'
         this.toLocalePublicationString({
           publishText: (comment.time + ' ago'),
-          templateString: this.$t('Video.Publicationtemplate'),
-          timeStrings: this.$t('Video.Published'),
-          liveStreamString: this.$t('Video.Watching'),
-          upcomingString: this.$t('Video.Published.Upcoming'),
           isLive: false,
           isUpcoming: false,
           isRSS: false
diff --git a/src/renderer/components/watch-video-comments/watch-video-comments.vue b/src/renderer/components/watch-video-comments/watch-video-comments.vue
index e4a68523..9edf6704 100644
--- a/src/renderer/components/watch-video-comments/watch-video-comments.vue
+++ b/src/renderer/components/watch-video-comments/watch-video-comments.vue
@@ -118,7 +118,7 @@
             {{ comment.numReplies }}
             {{ $t("Comments.Reply").toLowerCase() }}
             {{ $t("Comments.Replies").toLowerCase() }}
-             {{ $t("Comments.From $channelName").replace("$channelName", channelName) }}
+             {{ $t("Comments.From {channelName}", { channelName }) }}
             {{ $t("Comments.And others") }}
           
         
diff --git a/src/renderer/components/watch-video-info/watch-video-info.js b/src/renderer/components/watch-video-info/watch-video-info.js
index f7bd40a6..3c15801b 100644
--- a/src/renderer/components/watch-video-info/watch-video-info.js
+++ b/src/renderer/components/watch-video-info/watch-video-info.js
@@ -318,7 +318,6 @@ export default Vue.extend({
       this.$emit('pause-player')
 
       this.openInExternalPlayer({
-        strings: this.$t('Video.External Player'),
         watchProgress: this.getTimestamp(),
         playbackRate: this.defaultPlayback,
         videoId: this.id,
@@ -387,9 +386,8 @@ export default Vue.extend({
           })
 
           if (duplicateSubscriptions > 0) {
-            const message = this.$t('Channel.Removed subscription from $ other channel(s)')
             this.showToast({
-              message: message.replace('$', duplicateSubscriptions)
+              message: this.$t('Channel.Removed subscription from {count} other channel(s)', { count: duplicateSubscriptions })
             })
           }
         }
diff --git a/src/renderer/components/watch-video-info/watch-video-info.vue b/src/renderer/components/watch-video-info/watch-video-info.vue
index b759dc74..ffd362e3 100644
--- a/src/renderer/components/watch-video-info/watch-video-info.vue
+++ b/src/renderer/components/watch-video-info/watch-video-info.vue
@@ -97,7 +97,7 @@
         />
          {
@@ -292,7 +288,7 @@ const actions = {
         })
       } else {
         dispatch('showToast', {
-          message: completedMessage
+          message: i18n.t('Downloading has completed', { videoTitle: title })
         })
       }
     })
@@ -709,10 +705,10 @@ const actions = {
 
   toLocalePublicationString ({ dispatch }, payload) {
     if (payload.isLive) {
-      return '0' + payload.liveStreamString
+      return '0' + i18n.t('Video.Watching')
     } else if (payload.isUpcoming || payload.publishText === null) {
       // the check for null is currently just an inferring of knowledge, because there is no other possibility left
-      return `${payload.upcomingString}: ${payload.publishText}`
+      return `${i18n.t('Video.Published.Upcoming')}: ${payload.publishText}`
     } else if (payload.isRSS) {
       return payload.publishText
     }
@@ -722,59 +718,59 @@ const actions = {
       strings.shift()
     }
     const singular = (strings[0] === '1')
-    let publicationString = payload.templateString.replace('$', strings[0])
+    let unit
     switch (strings[1].substring(0, 2)) {
       case 'se':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Second)
+          unit = i18n.t('Video.Published.Second')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Seconds)
+          unit = i18n.t('Video.Published.Seconds')
         }
         break
       case 'mi':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Minute)
+          unit = i18n.t('Video.Published.Minute')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Minutes)
+          unit = i18n.t('Video.Published.Minutes')
         }
         break
       case 'ho':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Hour)
+          unit = i18n.t('Video.Published.Hour')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Hours)
+          unit = i18n.t('Video.Published.Hours')
         }
         break
       case 'da':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Day)
+          unit = i18n.t('Video.Published.Day')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Days)
+          unit = i18n.t('Video.Published.Days')
         }
         break
       case 'we':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Week)
+          unit = i18n.t('Video.Published.Week')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Weeks)
+          unit = i18n.t('Video.Published.Weeks')
         }
         break
       case 'mo':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Month)
+          unit = i18n.t('Video.Published.Month')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Months)
+          unit = i18n.t('Video.Published.Months')
         }
         break
       case 'ye':
         if (singular) {
-          publicationString = publicationString.replace('%', payload.timeStrings.Year)
+          unit = i18n.t('Video.Published.Year')
         } else {
-          publicationString = publicationString.replace('%', payload.timeStrings.Years)
+          unit = i18n.t('Video.Published.Years')
         }
         break
     }
-    return publicationString
+    return i18n.t('Video.Publicationtemplate', { number: strings[0], unit })
   },
 
   clearSessionSearchHistory ({ commit }) {
@@ -785,15 +781,10 @@ const actions = {
     FtToastEvents.$emit('toast-open', payload.message, payload.action, payload.time)
   },
 
-  showExternalPlayerUnsupportedActionToast: function ({ dispatch }, payload) {
-    if (!payload.ignoreWarnings) {
-      const toastMessage = payload.template
-        .replace('$', payload.externalPlayer)
-        .replace('%', payload.action)
-      dispatch('showToast', {
-        message: toastMessage
-      })
-    }
+  showExternalPlayerUnsupportedActionToast: function ({ dispatch }, { externalPlayer, action }) {
+    dispatch('showToast', {
+      message: i18n.t('Video.External Player.UnsupportedActionTemplate', { externalPlayer, action })
+    })
   },
 
   getExternalPlayerCmdArgumentsData ({ commit }, payload) {
@@ -849,12 +840,10 @@ const actions = {
     if (payload.watchProgress > 0 && payload.watchProgress < payload.videoLength - 10) {
       if (typeof cmdArgs.startOffset === 'string') {
         args.push(`${cmdArgs.startOffset}${payload.watchProgress}`)
-      } else {
+      } else if (!ignoreWarnings) {
         dispatch('showExternalPlayerUnsupportedActionToast', {
-          ignoreWarnings,
           externalPlayer,
-          template: payload.strings.UnsupportedActionTemplate,
-          action: payload.strings['Unsupported Actions']['starting video at offset']
+          action: i18n.t('Video.External Player.Unsupported Actions.starting video at offset')
         })
       }
     }
@@ -862,12 +851,10 @@ const actions = {
     if (payload.playbackRate !== null) {
       if (typeof cmdArgs.playbackRate === 'string') {
         args.push(`${cmdArgs.playbackRate}${payload.playbackRate}`)
-      } else {
+      } else if (!ignoreWarnings) {
         dispatch('showExternalPlayerUnsupportedActionToast', {
-          ignoreWarnings,
           externalPlayer,
-          template: payload.strings.UnsupportedActionTemplate,
-          action: payload.strings['Unsupported Actions']['setting a playback rate']
+          action: i18n.t('Video.External Player.Unsupported Actions.setting a playback rate')
         })
       }
     }
@@ -877,12 +864,10 @@ const actions = {
       if (payload.playlistIndex !== null) {
         if (typeof cmdArgs.playlistIndex === 'string') {
           args.push(`${cmdArgs.playlistIndex}${payload.playlistIndex}`)
-        } else {
+        } else if (!ignoreWarnings) {
           dispatch('showExternalPlayerUnsupportedActionToast', {
-            ignoreWarnings,
             externalPlayer,
-            template: payload.strings.UnsupportedActionTemplate,
-            action: payload.strings['Unsupported Actions']['opening specific video in a playlist (falling back to opening the video)']
+            action: i18n.t('Video.External Player.Unsupported Actions.opening specific video in a playlist (falling back to opening the video)')
           })
         }
       }
@@ -890,12 +875,10 @@ const actions = {
       if (payload.playlistReverse) {
         if (typeof cmdArgs.playlistReverse === 'string') {
           args.push(cmdArgs.playlistReverse)
-        } else {
+        } else if (!ignoreWarnings) {
           dispatch('showExternalPlayerUnsupportedActionToast', {
-            ignoreWarnings,
             externalPlayer,
-            template: payload.strings.UnsupportedActionTemplate,
-            action: payload.strings['Unsupported Actions']['reversing playlists']
+            action: i18n.t('Video.External Player.Unsupported Actions.reversing playlists')
           })
         }
       }
@@ -903,12 +886,10 @@ const actions = {
       if (payload.playlistShuffle) {
         if (typeof cmdArgs.playlistShuffle === 'string') {
           args.push(cmdArgs.playlistShuffle)
-        } else {
+        } else if (!ignoreWarnings) {
           dispatch('showExternalPlayerUnsupportedActionToast', {
-            ignoreWarnings,
             externalPlayer,
-            template: payload.strings.UnsupportedActionTemplate,
-            action: payload.strings['Unsupported Actions']['shuffling playlists']
+            action: i18n.t('Video.External Player.Unsupported Actions.shuffling playlists')
           })
         }
       }
@@ -916,12 +897,10 @@ const actions = {
       if (payload.playlistLoop) {
         if (typeof cmdArgs.playlistLoop === 'string') {
           args.push(cmdArgs.playlistLoop)
-        } else {
+        } else if (!ignoreWarnings) {
           dispatch('showExternalPlayerUnsupportedActionToast', {
-            ignoreWarnings,
             externalPlayer,
-            template: payload.strings.UnsupportedActionTemplate,
-            action: payload.strings['Unsupported Actions']['looping playlists']
+            action: i18n.t('Video.External Player.Unsupported Actions.looping playlists')
           })
         }
       }
@@ -931,12 +910,10 @@ const actions = {
         args.push(`${cmdArgs.playlistUrl}https://youtube.com/playlist?list=${payload.playlistId}`)
       }
     } else {
-      if (payload.playlistId !== null && payload.playlistId !== '') {
+      if (payload.playlistId !== null && payload.playlistId !== '' && !ignoreWarnings) {
         dispatch('showExternalPlayerUnsupportedActionToast', {
-          ignoreWarnings,
           externalPlayer,
-          template: payload.strings.UnsupportedActionTemplate,
-          action: payload.strings['Unsupported Actions']['opening playlists']
+          action: i18n.t('Video.External Player.Unsupported Actions.opening playlists')
         })
       }
       if (payload.videoId !== null) {
@@ -948,13 +925,12 @@ const actions = {
       }
     }
 
-    const openingToast = payload.strings.OpeningTemplate
-      .replace('$', payload.playlistId === null || payload.playlistId === ''
-        ? payload.strings.video
-        : payload.strings.playlist)
-      .replace('%', externalPlayer)
+    const videoOrPlaylist = payload.playlistId === null || payload.playlistId === ''
+      ? i18n.t('Video.External Player.video')
+      : i18n.t('Video.External Player.playlist')
+
     dispatch('showToast', {
-      message: openingToast
+      message: i18n.t('Video.External Player.OpeningTemplate', { videoOrPlaylist, externalPlayer })
     })
 
     const { ipcRenderer } = require('electron')
diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js
index 9bf3d3b7..228edfdc 100644
--- a/src/renderer/views/Channel/Channel.js
+++ b/src/renderer/views/Channel/Channel.js
@@ -623,9 +623,8 @@ export default Vue.extend({
           })
 
           if (duplicateSubscriptions > 0) {
-            const message = this.$t('Channel.Removed subscription from $ other channel(s)')
             this.showToast({
-              message: message.replace('$', duplicateSubscriptions)
+              message: this.$t('Channel.Removed subscription from {count} other channel(s)', { count: duplicateSubscriptions })
             })
           }
         }
diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.js b/src/renderer/views/SubscribedChannels/SubscribedChannels.js
index fbe49b2f..f1b57f27 100644
--- a/src/renderer/views/SubscribedChannels/SubscribedChannels.js
+++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.js
@@ -140,7 +140,7 @@ export default Vue.extend({
 
       this.updateProfile(currentProfile)
       this.showToast({
-        message: this.$t('Channels.Unsubscribed').replace('$', this.channelToUnsubscribe.name)
+        message: this.$t('Channels.Unsubscribed', { channelName: this.channelToUnsubscribe.name })
       })
 
       index = this.subscribedChannels.findIndex(channel => {
@@ -160,7 +160,7 @@ export default Vue.extend({
 
     thumbnailURL: function(originalURL) {
       let newURL = originalURL
-      if (originalURL.indexOf('ggpht.com') > -1) {
+      if (new URL(originalURL).hostname === 'yt3.ggpht.com') {
         if (this.backendPreference === 'invidious') { // YT to IV
           newURL = originalURL.replace(this.re.ytToIv, `${this.currentInvidiousInstance}/ggpht/$1`)
         }
diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue
index 288f9715..28189fba 100644
--- a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue
+++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue
@@ -21,7 +21,7 @@
       
       
         
-          {{ $t('Channels.Count').replace('$', channelList.length) }}
+          {{ $t('Channels.Count', { number: channelList.length }) }}