From 428c300e9f42f8b9232f780d387292c1a94fcd23 Mon Sep 17 00:00:00 2001
From: Rémy Raes <contact@remyraes.com>
Date: Wed, 29 Mar 2023 00:45:16 +0200
Subject: feat: i18n (#182)

* build: add vue-i18n dependency

* feat: add i18n plugin to vue project

* feat: use translations in play button

* feat: translate play view

* feat: translate menu items

* feat: translate local mods view

* feat: translate online mods view

* feat: translate mods menu

* feat: translate thunderstore mod card component

* fix: remove useless "this" keyword

* feat: translate settings view

* fix: remove leftover test invocation

* feat: add language selector component

* feat: using language selector updates interface's language

* feat: save language in persistent store on selector change

* feat: initialize lang on app launch

* refactor: move i18n code into App.mounted callback

* feat: update interface language on app launch

* feat: adjust language selection on language selector load

* fix: this.$root can't be null

* feat: translate store notifications

* fix: add missing parameter to english translation

* feat: translate "by" author keyword

* feat: translate repair window

* feat: translate repair window title

* docs: add some documentation regarding localization

* docs: explain how to add a new language

* feat: translate Northstar release canal selector elements

* docs: describe how to inject variable into translations

* feat: translate "info" word

* feat: translate popconfirm buttons

* fix: remove "this" keyword

* fix: save store when updating interface language
---
 src-vue/src/components/ThunderstoreModCard.vue | 34 +++++++++++++-------------
 1 file changed, 17 insertions(+), 17 deletions(-)

(limited to 'src-vue/src/components/ThunderstoreModCard.vue')

diff --git a/src-vue/src/components/ThunderstoreModCard.vue b/src-vue/src/components/ThunderstoreModCard.vue
index c9f6768c..b81ed03c 100644
--- a/src-vue/src/components/ThunderstoreModCard.vue
+++ b/src-vue/src/components/ThunderstoreModCard.vue
@@ -21,7 +21,7 @@
             <br/>
 
             <div class="name hide-text-overflow">{{ mod.name }}</div>
-            <div class="author hide-text-overflow">by {{ mod.owner }}</div>
+            <div class="author hide-text-overflow">{{ $t('mods.card.by') }} {{ mod.owner }}</div>
             <div class="desc">
                 {{ latestVersion.description }}
             </div>
@@ -33,7 +33,7 @@
                     :loading="isBeingInstalled || isBeingUpdated"
                     @click.stop="installMod(mod)"
                 >
-                    {{ modButtonText }}
+                    {{ $t(modButtonText) }}
                 </el-button>
 
                 <!-- Information dropdown menu -->
@@ -51,10 +51,10 @@
                     <template #dropdown>
                         <el-dropdown-menu>
                             <el-dropdown-item @click="openURL(mod.package_url)">
-                                More info
+                                {{ $t('mods.card.more_info') }}
                             </el-dropdown-item>
                             <el-dropdown-item  @click="deleteMod(mod)">
-                                Remove mod
+                                {{ $t('mods.card.remove') }}
                             </el-dropdown-item>
                         </el-dropdown-menu>
                     </template>
@@ -129,15 +129,15 @@ export default defineComponent({
         modButtonText(): string {
             switch (this.modStatus) {
                 case ThunderstoreModStatus.BEING_INSTALLED:
-                    return "Installing...";
+                    return "mods.card.button.being_installed";
                 case ThunderstoreModStatus.BEING_UPDATED:
-                    return "Updating...";
+                    return "mods.card.button.being_updated";
                 case ThunderstoreModStatus.INSTALLED:
-                    return "Installed";
+                    return "mods.card.button.installed";
                 case ThunderstoreModStatus.NOT_INSTALLED:
-                    return "Install";
+                    return "mods.card.button.install";
                 case ThunderstoreModStatus.OUTDATED:
-                    return "Update";
+                    return "mods.card.button.outdated";
             }
         },
 
@@ -200,11 +200,11 @@ export default defineComponent({
 
             // Show pop-up to confirm delete
             ElMessageBox.confirm(
-                'Delete Thunderstore mod?',
-                'Warning',
+                this.$t('mods.card.remove_dialog_text'),
+                this.$t('mods.card.remove_dialog_title'),
                 {
-                    confirmButtonText: 'OK',
-                    cancelButtonText: 'Cancel',
+                    confirmButtonText: this.$t('generic.yes'),
+                    cancelButtonText: this.$t('generic.cancel'),
                     type: 'warning',
                 }
             )
@@ -217,7 +217,7 @@ export default defineComponent({
                     await invoke("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name })
                         .then((message) => {
                             ElNotification({
-                                title: `Removed ${mod.name}`,
+                                title: this.$t('mods.card.remove_success', {modName: mod.name}),
                                 message: message as string,
                                 type: 'success',
                                 position: 'bottom-right'
@@ -225,7 +225,7 @@ export default defineComponent({
                         })
                         .catch((error) => {
                             ElNotification({
-                                title: 'Error',
+                                title: this.$t('generic.error'),
                                 message: error,
                                 type: 'error',
                                 position: 'bottom-right'
@@ -255,7 +255,7 @@ export default defineComponent({
 
             await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => {
                 ElNotification({
-                    title: `Installed ${mod.name}`,
+                    title: this.$t('mods.card.install_success', {modName: mod.name}),
                     message: message as string,
                     type: 'success',
                     position: 'bottom-right'
@@ -263,7 +263,7 @@ export default defineComponent({
             })
                 .catch((error) => {
                     ElNotification({
-                        title: 'Error',
+                        title: this.$t('generic.error'),
                         message: error,
                         type: 'error',
                         position: 'bottom-right'
-- 
cgit v1.2.3


From 6d58e6793d40df4fec518f37351868803a02a033 Mon Sep 17 00:00:00 2001
From: Rémy Raes <contact@remyraes.com>
Date: Mon, 10 Apr 2023 14:25:32 +0200
Subject: refactor: Notifications (#259)

* feat: wrap ElNotification class in a showNotification method

* refactor: replace notification invocations in mod views

* feat: add 'info' type to showNotification method

* refactor: replace notification invocations in repair view

* refactor: replace notification invocations in settings view

* refactor: replace notification invocations in developer view

* feat: showNotification method now returns a NotificationHandle

* feat: showNotification has a duration argument

* refactor: replace notification invocations in pull requests module

* refactor: replace notification invocation in repair view

* refactor: replace notification invocations in UI store

* refactor: remove unused import from play view

* refactor: use showErrorNotification method to wrap up error display

* fix: add missing showErrorNotification imports

* style: format code

* style: format ThunderstoreModCard.vue

* style: format pull_requests.ts

* style: format SettingsView.vue

* style: format RepairView.vue

* style: format DeveloperView.vue

* refactor: remove useless import

* refactor: add missing showErrorNotification invocation

* feat: showErrorNotification has an optional title argument

* style: format LocalModsView.vue
---
 src-vue/src/components/ThunderstoreModCard.vue | 34 +++-------
 src-vue/src/plugins/modules/pull_requests.ts   | 66 +++++---------------
 src-vue/src/plugins/store.ts                   | 86 +++++++++-----------------
 src-vue/src/utils/ui.ts                        | 29 +++++++++
 src-vue/src/views/DeveloperView.vue            | 81 ++++--------------------
 src-vue/src/views/PlayView.vue                 |  1 -
 src-vue/src/views/RepairView.vue               | 57 ++++-------------
 src-vue/src/views/SettingsView.vue             | 20 +++---
 src-vue/src/views/mods/LocalModsView.vue       | 22 ++-----
 9 files changed, 117 insertions(+), 279 deletions(-)
 create mode 100644 src-vue/src/utils/ui.ts

(limited to 'src-vue/src/components/ThunderstoreModCard.vue')

diff --git a/src-vue/src/components/ThunderstoreModCard.vue b/src-vue/src/components/ThunderstoreModCard.vue
index b81ed03c..fec95f14 100644
--- a/src-vue/src/components/ThunderstoreModCard.vue
+++ b/src-vue/src/components/ThunderstoreModCard.vue
@@ -72,9 +72,9 @@ import {invoke, shell} from "@tauri-apps/api";
 import {ThunderstoreModStatus} from "../utils/thunderstore/ThunderstoreModStatus";
 import {NorthstarMod} from "../../../src-tauri/bindings/NorthstarMod";
 import {GameInstall} from "../utils/GameInstall";
-import {ElNotification} from "element-plus";
 import { NorthstarState } from "../utils/NorthstarState";
 import { ElMessageBox } from "element-plus";
+import { showErrorNotification, showNotification } from "../utils/ui";
 
 export default defineComponent({
     name: "ThunderstoreModCard",
@@ -214,22 +214,12 @@ export default defineComponent({
                         install_type: this.$store.state.install_type
                     } as GameInstall;
 
-                    await invoke("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name })
+                    await invoke<string>("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name })
                         .then((message) => {
-                            ElNotification({
-                                title: this.$t('mods.card.remove_success', {modName: mod.name}),
-                                message: message as string,
-                                type: 'success',
-                                position: 'bottom-right'
-                            });
+                            showNotification(this.$t('mods.card.remove_success', {modName: mod.name}), message);
                         })
                         .catch((error) => {
-                            ElNotification({
-                                title: this.$t('generic.error'),
-                                message: error,
-                                type: 'error',
-                                position: 'bottom-right'
-                            });
+                            showErrorNotification(error);
                         })
                         .finally(() => {
                             this.$store.commit('loadInstalledMods');
@@ -253,21 +243,11 @@ export default defineComponent({
                 this.isBeingInstalled = true;
             }
 
-            await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => {
-                ElNotification({
-                    title: this.$t('mods.card.install_success', {modName: mod.name}),
-                    message: message as string,
-                    type: 'success',
-                    position: 'bottom-right'
-                });
+            await invoke<string>("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => {
+                showNotification(this.$t('mods.card.install_success', {modName: mod.name}), message);
             })
                 .catch((error) => {
-                    ElNotification({
-                        title: this.$t('generic.error'),
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 })
                 .finally(() => {
                     this.isBeingInstalled = false;
diff --git a/src-vue/src/plugins/modules/pull_requests.ts b/src-vue/src/plugins/modules/pull_requests.ts
index 227888f2..ff9ec322 100644
--- a/src-vue/src/plugins/modules/pull_requests.ts
+++ b/src-vue/src/plugins/modules/pull_requests.ts
@@ -1,8 +1,8 @@
-import { ElNotification } from "element-plus";
 import { invoke, shell } from "@tauri-apps/api";
 import { PullsApiResponseElement } from "../../../../src-tauri/bindings/PullsApiResponseElement";
 import { PullRequestType } from '../../../../src-tauri/bindings/PullRequestType';
 import { store } from "../store";
+import { showErrorNotification, showNotification } from "../../utils/ui";
 
 interface PullRequestStoreState {
     searchValue: string,
@@ -33,12 +33,7 @@ export const pullRequestModule = {
                     }
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async downloadLauncherPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) {
@@ -48,12 +43,7 @@ export const pullRequestModule = {
                     shell.open(url);
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async downloadModsPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) {
@@ -62,32 +52,16 @@ export const pullRequestModule = {
         },
         async installLauncherPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) {
             // Send notification telling the user to wait for the process to finish
-            const notification = ElNotification({
-                title: `Installing launcher PR ${pull_request.number}`,
-                message: 'Please wait',
-                duration: 0,
-                type: 'info',
-                position: 'bottom-right'
-            });
+            const notification = showNotification(`Installing launcher PR ${pull_request.number}`, 'Please wait', 'info', 0);
 
             await invoke("apply_launcher_pr", { pullRequest: pull_request, gameInstallPath: store.state.game_path })
                 .then((message) => {
                     console.log(message);
                     // Show user notification if mod install completed.
-                    ElNotification({
-                        title: `Done`,
-                        message: `Installed ${pull_request.number}: "${pull_request.title}"`,
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification(`Done`, `Installed ${pull_request.number}: "${pull_request.title}"`);
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 })
                 .finally(() => {
                     // Clear old notification
@@ -96,32 +70,20 @@ export const pullRequestModule = {
         },
         async installModsPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) {
             // Send notification telling the user to wait for the process to finish
-            const notification = ElNotification({
-                title: `Installing mods PR ${pull_request.number}`,
-                message: 'Please wait',
-                duration: 0,
-                type: 'info',
-                position: 'bottom-right'
-            });
+            const notification = showNotification(`Installing mods PR ${pull_request.number}`, 'Please wait', 'info', 0);
 
             await invoke("apply_mods_pr", { pullRequest: pull_request, gameInstallPath: store.state.game_path })
                 .then((message) => {
                     // Show user notification if mod install completed.
-                    ElNotification({
-                        title: `Done`,
-                        message: `Installed ${pull_request.number}: "${pull_request.title}"\nMake sure to launch via batch file or by specifying correct profile!`,
-                        type: 'success',
-                        duration: 7_000, // in ms
-                        position: 'bottom-right'
-                    });
+                    showNotification(
+                        `Done`,
+                        `Installed ${pull_request.number}: "${pull_request.title}"\nMake sure to launch via batch file or by specifying correct profile!`,
+                        'success',
+                        7000
+                    );
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 })
                 .finally(() => {
                     // Clear old notification
diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts
index cdbd6738..6f878277 100644
--- a/src-vue/src/plugins/store.ts
+++ b/src-vue/src/plugins/store.ts
@@ -6,7 +6,7 @@ import { invoke } from "@tauri-apps/api";
 import { GameInstall } from "../utils/GameInstall";
 import { ReleaseCanal } from "../utils/ReleaseCanal";
 import { FlightCoreVersion } from "../../../src-tauri/bindings/FlightCoreVersion";
-import { ElNotification, NotificationHandle } from 'element-plus';
+import { NotificationHandle } from 'element-plus';
 import { NorthstarState } from '../utils/NorthstarState';
 import { appDir } from '@tauri-apps/api/path';
 import { open } from '@tauri-apps/api/dialog';
@@ -18,6 +18,7 @@ import { NorthstarMod } from "../../../src-tauri/bindings/NorthstarMod";
 import { searchModule } from './modules/search';
 import { i18n } from '../main';
 import { pullRequestModule } from './modules/pull_requests';
+import { showErrorNotification, showNotification } from '../utils/ui';
 
 const persistentStore = new Store('flight-core-settings.json');
 
@@ -128,12 +129,10 @@ export const store = createStore<FlightCoreStore>({
                 let is_valid_titanfall2_install = await invoke("verify_install_location", { gamePath: selected }) as boolean;
                 if (is_valid_titanfall2_install) {
                     state.game_path = selected;
-                    ElNotification({
-                        title: i18n.global.tc('notification.game_folder.new.title'),
-                        message: i18n.global.tc('notification.game_folder.new.text'),
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification(
+                        i18n.global.tc('notification.game_folder.new.title'),
+                        i18n.global.tc('notification.game_folder.new.text')
+                    );
                     try {
                         notification_handle.close();
                     }
@@ -156,12 +155,10 @@ export const store = createStore<FlightCoreStore>({
                 }
                 else {
                     // Not valid Titanfall2 install
-                    ElNotification({
-                        title: i18n.global.tc('notification.game_folder.wrong.title'),
-                        message: i18n.global.tc('notification.game_folder.wrong.text'),
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(
+                        i18n.global.tc('notification.game_folder.wrong.text'),
+                        i18n.global.tc('notification.game_folder.wrong.title')
+                    );
                 }
             }
         },
@@ -229,12 +226,7 @@ export const store = createStore<FlightCoreStore>({
                         })
                         .catch((error) => {
                             console.error(error);
-                            ElNotification({
-                                title: i18n.global.tc('generic.error'),
-                                message: error,
-                                type: 'error',
-                                position: 'bottom-right'
-                            });
+                            showErrorNotification(error);
                         });
                     break;
 
@@ -251,20 +243,11 @@ export const store = createStore<FlightCoreStore>({
 
             await invoke("launch_northstar_steam_caller", { gameInstall: game_install, bypassChecks: no_checks })
                 .then((message) => {
-                    ElNotification({
-                        title: 'Success',
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification('Success');
                 })
                 .catch((error) => {
                     console.error(error);
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
 
             return;
@@ -324,12 +307,7 @@ export const store = createStore<FlightCoreStore>({
                 })
                 .catch((error) => {
                     console.error(error);
-                    ElNotification({
-                        title: i18n.global.tc('generic.error'),
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async toggleReleaseCandidate(state: FlightCoreStore) {
@@ -346,12 +324,10 @@ export const store = createStore<FlightCoreStore>({
             store.commit("checkNorthstarUpdates");
 
             // Display notification to highlight change
-            ElNotification({
-                title: i18n.global.tc(`channels.names.${state.northstar_release_canal}`),
-                message: i18n.global.tc('channels.release.switch.text', {canal: state.northstar_release_canal}),
-                type: 'success',
-                position: 'bottom-right'
-            });
+            showNotification(
+                i18n.global.tc(`channels.names.${state.northstar_release_canal}`),
+                i18n.global.tc('channels.release.switch.text', {canal: state.northstar_release_canal}),
+            );
         }
     }
 });
@@ -418,13 +394,12 @@ async function _initializeApp(state: any) {
             .catch((err) => {
                 // Gamepath not found or other error
                 console.error(err);
-                notification_handle = ElNotification({
-                    title: i18n.global.tc('notification.game_folder.not_found.title'),
-                    message: i18n.global.tc('notification.game_folder.not_found.text'),
-                    type: 'error',
-                    position: 'bottom-right',
-                    duration: 0 // Duration `0` means the notification will not auto-vanish
-                });
+                notification_handle = showNotification(
+                    i18n.global.tc('notification.game_folder.not_found.title'),
+                    i18n.global.tc('notification.game_folder.not_found.text'),
+                    'error',
+                    0   // Duration `0` means the notification will not auto-vanish
+                );
             });
     }
 
@@ -461,13 +436,12 @@ async function _checkForFlightCoreUpdates(state: FlightCoreStore) {
 
     if (flightcore_is_outdated) {
         let newest_flightcore_version = await invoke("get_newest_flightcore_version") as FlightCoreVersion;
-        ElNotification({
-            title: i18n.global.tc('notification.flightcore_outdated.title'),
-            message: i18n.global.tc('notification.flightcore_outdated.text', {oldVersion: state.flightcore_version, newVersion: newest_flightcore_version.tag_name}),
-            type: 'warning',
-            position: 'bottom-right',
-            duration: 0 // Duration `0` means the notification will not auto-vanish
-        });
+        showNotification(
+            i18n.global.tc('notification.flightcore_outdated.title'),
+            i18n.global.tc('notification.flightcore_outdated.text', {oldVersion: state.flightcore_version, newVersion: newest_flightcore_version.tag_name}),
+            'warning',
+            0 // Duration `0` means the notification will not auto-vanish
+        );
     }
 }
 
diff --git a/src-vue/src/utils/ui.ts b/src-vue/src/utils/ui.ts
new file mode 100644
index 00000000..b84d7666
--- /dev/null
+++ b/src-vue/src/utils/ui.ts
@@ -0,0 +1,29 @@
+import { ElNotification, NotificationHandle } from "element-plus";
+import { i18n } from "../main";
+
+/**
+ * Displays content to the user in the form of a notification appearing on screen bottom right.
+ **/
+function showNotification(
+    title: string,
+    message: string = '',
+    type: 'success' | 'warning' | 'error' | 'info' = 'success',
+    duration: number = 4500
+): NotificationHandle {
+    return ElNotification({
+        title, message, type, duration,
+        position: 'bottom-right',
+    });
+}
+
+/**
+ * Helper method displaying an error message to the user.
+ **/
+function showErrorNotification(
+    error: string,
+    title: string = i18n.global.tc('generic.error')
+): NotificationHandle {
+    return showNotification(title, error, 'error');
+}
+
+export {showNotification, showErrorNotification};
diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue
index 7712756c..d9e268e3 100644
--- a/src-vue/src/views/DeveloperView.vue
+++ b/src-vue/src/views/DeveloperView.vue
@@ -87,10 +87,10 @@
 <script lang="ts">
 import { defineComponent } from "vue";
 import { invoke } from "@tauri-apps/api";
-import { ElNotification } from "element-plus";
 import { GameInstall } from "../utils/GameInstall";
 import { TagWrapper } from "../../../src-tauri/bindings/TagWrapper";
 import PullRequestsSelector from "../components/PullRequestsSelector.vue";
+import { showErrorNotification, showNotification } from "../utils/ui";
 
 export default defineComponent({
     name: "DeveloperView",
@@ -130,30 +130,15 @@ export default defineComponent({
         },
         async crashApplication() {
             await invoke("force_panic");
-            ElNotification({
-                title: 'Error',
-                message: "Never should have been able to get here!",
-                type: 'error',
-                position: 'bottom-right'
-            });
+            showErrorNotification("Never should have been able to get here!");
         },
         async checkLinuxCompatibility() {
             await invoke("linux_checks")
                 .then(() => {
-                    ElNotification({
-                        title: 'Linux compatible',
-                        message: 'All checks passed',
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification('Linux compatible', 'All checks passed');
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Not linux compatible',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showNotification('Not Linux compatible', error, 'error');
                     console.error(error);
                 });
         },
@@ -174,20 +159,10 @@ export default defineComponent({
                 console.log(message);
 
                 // Just a visual indicator that it worked
-                ElNotification({
-                    title: 'Success',
-                    message: "Success",
-                    type: 'success',
-                    position: 'bottom-right'
-                });
+                showNotification('Success');
             })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async installMod() {
@@ -196,62 +171,32 @@ export default defineComponent({
                 install_type: this.$store.state.install_type
             } as GameInstall;
             let mod_to_install = this.mod_to_install_field_string;
-            await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => {
+            await invoke<string>("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => {
                 // Show user notification if mod install completed.
-                ElNotification({
-                    title: `Installed ${mod_to_install}`,
-                    message: message as string,
-                    type: 'success',
-                    position: 'bottom-right'
-                });
+                showNotification(`Installed ${mod_to_install}`, message);
             })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async getTags() {
             await invoke<TagWrapper[]>("get_list_of_tags")
                 .then((message) => {
                     this.ns_release_tags = message;
-                    ElNotification({
-                        title: "Done",
-                        message: "Fetched tags",
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification("Done", "Fetched tags");
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async compareTags() {
             await invoke<string>("compare_tags", {firstTag: this.firstTag.value, secondTag: this.secondTag.value})
                 .then((message) => {
                     this.release_notes_text = message;
-                    ElNotification({
-                        title: "Done",
-                        message: "Generated release notes",
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification("Done", "Generated release notes");
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
     }
diff --git a/src-vue/src/views/PlayView.vue b/src-vue/src/views/PlayView.vue
index 57d02904..2f3562ef 100644
--- a/src-vue/src/views/PlayView.vue
+++ b/src-vue/src/views/PlayView.vue
@@ -1,5 +1,4 @@
 <script lang="ts">
-import { ElNotification } from 'element-plus';
 import { Tabs } from "../utils/Tabs";
 import PlayButton from '../components/PlayButton.vue';
 import { defineComponent } from "vue";
diff --git a/src-vue/src/views/RepairView.vue b/src-vue/src/views/RepairView.vue
index 12224524..9de9a7f3 100644
--- a/src-vue/src/views/RepairView.vue
+++ b/src-vue/src/views/RepairView.vue
@@ -32,11 +32,11 @@
 
 <script lang="ts">
 import { defineComponent } from "vue";
-import { ElNotification } from "element-plus";
 import { GameInstall } from "../utils/GameInstall";
 import { invoke } from "@tauri-apps/api";
 import { ReleaseCanal } from "../utils/ReleaseCanal";
 import { Store } from 'tauri-plugin-store-api';
+import { showErrorNotification, showNotification } from "../utils/ui";
 import { appWindow } from "@tauri-apps/api/window";
 const persistentStore = new Store('flight-core-settings.json');
 
@@ -55,20 +55,10 @@ export default defineComponent({
             } as GameInstall;
             await invoke("disable_all_but_core", { gameInstall: game_install })
                 .then((message) => {
-                    ElNotification({
-                        title: 'Success',
-                        message: "Disabled all mods but core",
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification('Success', "Disabled all mods but core");
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async forceInstallNorthstar() {
@@ -78,33 +68,22 @@ export default defineComponent({
             } as GameInstall;
 
             // Send notification telling the user to wait for the process to finish
-            const notification = ElNotification({
-                title: 'Force reinstalling Northstar',
-                message: 'Please wait',
-                duration: 0,
-                type: 'info',
-                position: 'bottom-right'
-            });
+            const notification = showNotification(
+                'Force reinstalling Northstar',
+                'Please wait',
+                'info',
+                0
+            );
 
             let install_northstar_result = invoke("install_northstar_caller", { gamePath: game_install.game_path, northstarPackageName: ReleaseCanal.RELEASE });
             await install_northstar_result
                 .then((message) => {
                     // Send notification
-                    ElNotification({
-                        title: `Done`,
-                        message: `Successfully reinstalled Northstar`,
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification('Done', `Successfully reinstalled Northstar`);
                     this.$store.commit('checkNorthstarUpdates');
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                     console.error(error);
                 })
                 .finally(() => {
@@ -119,20 +98,10 @@ export default defineComponent({
             } as GameInstall;
             await invoke("clean_up_download_folder_caller", { gameInstall: game_install, force: true }).then((message) => {
                 // Show user notification if task completed.
-                ElNotification({
-                    title: `Done`,
-                    message: `Done`,
-                    type: 'success',
-                    position: 'bottom-right'
-                });
+                showNotification('Done', 'Done');
             })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
         async clearFlightCorePersistentStore() {
diff --git a/src-vue/src/views/SettingsView.vue b/src-vue/src/views/SettingsView.vue
index e526f821..772a4c5c 100644
--- a/src-vue/src/views/SettingsView.vue
+++ b/src-vue/src/views/SettingsView.vue
@@ -72,10 +72,10 @@
 
 <script lang="ts">
 import { defineComponent } from "vue";
-import { ElNotification } from 'element-plus';
 import { invoke } from "@tauri-apps/api";
 import { ReleaseCanal } from "../utils/ReleaseCanal";
 import { Store } from 'tauri-plugin-store-api';
+import { showErrorNotification, showNotification } from "../utils/ui";
 import LanguageSelector from "../components/LanguageSelector.vue";
 const persistentStore = new Store('flight-core-settings.json');
 
@@ -125,12 +125,11 @@ export default defineComponent({
             this.developerModeClicks += 1;
             if (this.developerModeClicks >= 6 && !this.$store.state.developer_mode) {
                 this.$store.commit('toggleDeveloperMode');
-                ElNotification({
-                    title: this.$t('settings.dev_mode_enabled_title'),
-                    message: this.$t('settings.dev_mode_enabled_text'),
-                    type: 'info',
-                    position: 'bottom-right'
-                });
+                showNotification(
+                    this.$t('settings.dev_mode_enabled_title'),
+                    this.$t('settings.dev_mode_enabled_text'),
+                    'info'
+                );
                 this.developerModeClicks = 0;
             }
         },
@@ -141,12 +140,7 @@ export default defineComponent({
             await invoke("open_repair_window")
                 .then((message) => { })
                 .catch((error) => {
-                    ElNotification({
-                        title: 'Error',
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 });
         },
     },
diff --git a/src-vue/src/views/mods/LocalModsView.vue b/src-vue/src/views/mods/LocalModsView.vue
index 2cd253ae..3fb90bdc 100644
--- a/src-vue/src/views/mods/LocalModsView.vue
+++ b/src-vue/src/views/mods/LocalModsView.vue
@@ -35,10 +35,10 @@
 
 <script lang="ts">
 import { invoke } from '@tauri-apps/api';
-import { ElNotification } from 'element-plus';
 import { defineComponent } from 'vue';
 import { GameInstall } from '../../utils/GameInstall';
 import { NorthstarMod } from "../../../../src-tauri/bindings/NorthstarMod";
+import { showErrorNotification, showNotification } from '../../utils/ui';
 
 export default defineComponent({
     name: 'LocalModsView',
@@ -85,12 +85,7 @@ export default defineComponent({
                 })
             }
             catch (error) {
-                ElNotification({
-                    title: this.$t('generic.error'),
-                    message: `${error}`,
-                    type: 'error',
-                    position: 'bottom-right'
-                });
+                showErrorNotification(`${error}`);
                 this.global_load_indicator = false;
                 return false;
             }
@@ -106,19 +101,10 @@ export default defineComponent({
             await invoke("delete_northstar_mod", { gameInstall: game_install, nsmodName: mod.name })
                 .then((message) => {
                     // Just a visual indicator that it worked
-                    ElNotification({
-                        title: this.$t('mods.local.success_deleting', {modName: mod.name}),
-                        type: 'success',
-                        position: 'bottom-right'
-                    });
+                    showNotification(this.$t('mods.local.success_deleting', {modName: mod.name}));
                 })
                 .catch((error) => {
-                    ElNotification({
-                        title: this.$t('generic.error'),
-                        message: error,
-                        type: 'error',
-                        position: 'bottom-right'
-                    });
+                    showErrorNotification(error);
                 })
                 .finally(() => {
                     this.$store.commit('loadInstalledMods');
-- 
cgit v1.2.3

