diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index bc6f073c3..9c23db537 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -3,10 +3,8 @@ using System.IO; using System.Linq; using System.Reflection; using System.Windows; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Win32; using Squirrel; @@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration { private static readonly string ClassName = nameof(Portable); - private readonly IPublicAPI API = Ioc.Default.GetRequiredService(); - /// /// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish /// @@ -45,13 +41,13 @@ namespace Flow.Launcher.Core.Configuration #endif IndicateDeletion(DataLocation.PortableDataPath); - API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode")); + PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode()); UpdateManager.RestartApp(Constant.ApplicationFileName); } catch (Exception e) { - API.LogException(ClassName, "Error occurred while disabling portable mode", e); + PublicApi.Instance.LogException(ClassName, "Error occurred while disabling portable mode", e); } } @@ -68,13 +64,13 @@ namespace Flow.Launcher.Core.Configuration #endif IndicateDeletion(DataLocation.RoamingDataPath); - API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode")); + PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode()); UpdateManager.RestartApp(Constant.ApplicationFileName); } catch (Exception e) { - API.LogException(ClassName, "Error occurred while enabling portable mode", e); + PublicApi.Instance.LogException(ClassName, "Error occurred while enabling portable mode", e); } } @@ -94,13 +90,13 @@ namespace Flow.Launcher.Core.Configuration public void MoveUserDataFolder(string fromLocation, string toLocation) { - FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s)); + FilesFolders.CopyAll(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s)); VerifyUserDataAfterMove(fromLocation, toLocation); } public void VerifyUserDataAfterMove(string fromLocation, string toLocation) { - FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s)); + FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s)); } public void CreateShortcuts() @@ -150,12 +146,12 @@ namespace Flow.Launcher.Core.Configuration // delete it and prompt the user to pick the portable data location if (File.Exists(roamingDataDeleteFilePath)) { - FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s)); + FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => PublicApi.Instance.ShowMsgBox(s)); - if (API.ShowMsgBox(API.GetTranslation("moveToDifferentLocation"), + if (PublicApi.Instance.ShowMsgBox(Localize.moveToDifferentLocation(), string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { - FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s)); + FilesFolders.OpenPath(Constant.RootDirectory, (s) => PublicApi.Instance.ShowMsgBox(s)); Environment.Exit(0); } @@ -164,9 +160,9 @@ namespace Flow.Launcher.Core.Configuration // delete it and notify the user about it. else if (File.Exists(portableDataDeleteFilePath)) { - FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s)); + FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => PublicApi.Instance.ShowMsgBox(s)); - API.ShowMsgBox(API.GetTranslation("shortcutsUninstallerCreated")); + PublicApi.Instance.ShowMsgBox(Localize.shortcutsUninstallerCreated()); } } @@ -177,8 +173,7 @@ namespace Flow.Launcher.Core.Configuration if (roamingLocationExists && portableLocationExists) { - API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"), - DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); + PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); return false; } diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs index 841099dd1..7c0290b2a 100644 --- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs +++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs @@ -8,7 +8,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Plugin; @@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins { private static readonly string ClassName = nameof(CommunityPluginSource); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - private string latestEtag = ""; - private List plugins = new(); + private List plugins = []; private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new() { @@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins /// public async Task> FetchAsync(CancellationToken token) { - API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}"); + PublicApi.Instance.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}"); var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl); @@ -59,40 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins .ConfigureAwait(false); latestEtag = response.Headers.ETag?.Tag; - API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}"); + PublicApi.Instance.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}"); return plugins; } else if (response.StatusCode == HttpStatusCode.NotModified) { - API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified."); + PublicApi.Instance.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified."); return plugins; } else { - API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); + PublicApi.Instance.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); return null; } } catch (OperationCanceledException) when (token.IsCancellationRequested) { - API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller."); + PublicApi.Instance.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller."); return null; } catch (TaskCanceledException) { // Likely an HttpClient timeout or external cancellation not requested by our token - API.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out."); + PublicApi.Instance.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out."); return null; } catch (Exception e) { if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) { - API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e); + PublicApi.Instance.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e); } else { - API.LogException(ClassName, "Error Occurred", e); + PublicApi.Instance.LogException(ClassName, "Error Occurred", e); } return null; } diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 14796a87a..1a324a993 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Windows; using System.Windows.Forms; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; @@ -15,7 +14,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { private static readonly string ClassName = nameof(AbstractPluginEnvironment); - protected readonly IPublicAPI API = Ioc.Default.GetRequiredService(); + protected readonly IPublicAPI API = PublicApi.Instance; internal abstract string Language { get; } @@ -58,15 +57,10 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments return SetPathForPluginPairs(PluginsSettingsFilePath, Language); } - var noRuntimeMessage = string.Format( - API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"), - Language, - EnvName, - Environment.NewLine - ); + var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine); if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) { - var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName); + var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName); var selectedFile = GetFileFromDialog(msg, FileDialogFilter); @@ -77,12 +71,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments // Nothing selected because user pressed cancel from the file dialog window else { - var forceDownloadMessage = string.Format( - API.GetTranslation("runtimeExecutableInvalidChooseDownload"), - Language, - EnvName, - Environment.NewLine - ); + var forceDownloadMessage = Localize.runtimeExecutableInvalidChooseDownload(Language, EnvName, Environment.NewLine); // Let users select valid path or choose to download while (string.IsNullOrEmpty(selectedFile)) @@ -120,7 +109,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } else { - API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language)); + API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language)); API.LogError(ClassName, $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"{Language}Environment"); @@ -248,7 +237,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments private static string GetUpdatedEnvironmentPath(string filePath) { var index = filePath.IndexOf(DataLocation.PluginEnvironments); - + // get the substring after "Environments" because we can not determine it dynamically var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..]; return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}"; diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs index 89286dfb0..76c775fb4 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs @@ -51,7 +51,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } catch (System.Exception e) { - API.ShowMsgError(API.GetTranslation("failToInstallPythonEnv")); + API.ShowMsgError(Localize.failToInstallPythonEnv()); API.LogException(ClassName, "Failed to install Python environment", e); } }); diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs index 724ae20f4..d8244cbf3 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs @@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } catch (System.Exception e) { - API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); + API.ShowMsgError(Localize.failToInstallTypeScriptEnv()); API.LogException(ClassName, "Failed to install TypeScript environment", e); } }); diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs index 6a32664a1..e2de53e39 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs @@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } catch (System.Exception e) { - API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); + API.ShowMsgError(Localize.failToInstallTypeScriptEnv()); API.LogException(ClassName, "Failed to install TypeScript environment", e); } }); diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs index 1e845498c..eab9a8c43 100644 --- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs +++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; using Flow.Launcher.Infrastructure; @@ -23,10 +22,6 @@ namespace Flow.Launcher.Core.ExternalPlugins private static DateTime lastFetchedAt = DateTime.MinValue; private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - public static List UserPlugins { get; private set; } public static async Task UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) @@ -61,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins } catch (Exception e) { - API.LogException(ClassName, "Http request failed", e); + PublicApi.Instance.LogException(ClassName, "Http request failed", e); } finally { @@ -83,12 +78,12 @@ namespace Flow.Launcher.Core.ExternalPlugins } catch (Exception e) { - API.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. " + PublicApi.Instance.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. " + "Plugin excluded from manifest", e); return false; } - API.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, " + PublicApi.Instance.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, " + $"but current version is {Constant.Version}. Plugin excluded from manifest."); return false; diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 06d458bd4..7bf90ea51 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -55,6 +56,7 @@ + @@ -63,6 +65,17 @@ + + + true + + + + + + Languages\en.xaml + + diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs index 9212dada6..abefd47bc 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -285,7 +285,7 @@ namespace Flow.Launcher.Core.Plugin HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Margin = SettingPanelItemLeftMargin, - Content = API.GetTranslation("select") + Content = Localize.select() }; Btn.Click += (_, _) => diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index cba9e315d..7c074c22e 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -5,8 +5,6 @@ using System.IO; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; using System.Text.Json; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher.Core.Plugin { @@ -14,10 +12,6 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginConfig); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - /// /// Parse plugin metadata in the given directories /// @@ -39,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Can't delete <{directory}>", e); + PublicApi.Instance.LogException(ClassName, $"Can't delete <{directory}>", e); } } else @@ -56,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin duplicateList .ForEach( - x => API.LogWarn(ClassName, + x => PublicApi.Instance.LogWarn(ClassName, string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " + "not loaded due to version not the highest of the duplicates", x.Name, x.ID, x.Version), @@ -108,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName); if (!File.Exists(configPath)) { - API.LogError(ClassName, $"Didn't find config file <{configPath}>"); + PublicApi.Instance.LogError(ClassName, $"Didn't find config file <{configPath}>"); return null; } @@ -124,19 +118,19 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Invalid json for config <{configPath}>", e); + PublicApi.Instance.LogException(ClassName, $"Invalid json for config <{configPath}>", e); return null; } if (!AllowedLanguage.IsAllowed(metadata.Language)) { - API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>"); + PublicApi.Instance.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>"); return null; } if (!File.Exists(metadata.ExecuteFilePath)) { - API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}"); + PublicApi.Instance.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}"); return null; } diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs index d01b34ab6..6027b712e 100644 --- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs +++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -22,10 +22,6 @@ public static class PluginInstaller private static readonly Settings Settings = Ioc.Default.GetRequiredService(); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - /// /// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed. /// @@ -33,18 +29,16 @@ public static class PluginInstaller /// A Task representing the asynchronous install operation. public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin) { - if (API.PluginModified(newPlugin.ID)) + if (PublicApi.Instance.PluginModified(newPlugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), newPlugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name), + Localize.pluginModifiedAlreadyMessage()); return; } - if (API.ShowMsgBox( - string.Format( - API.GetTranslation("InstallPromptSubtitle"), - newPlugin.Name, newPlugin.Author, Environment.NewLine), - API.GetTranslation("InstallPromptTitle"), + if (PublicApi.Instance.ShowMsgBox( + Localize.InstallPromptSubtitle(newPlugin.Name, newPlugin.Author, Environment.NewLine), + Localize.InstallPromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; try @@ -61,7 +55,7 @@ public static class PluginInstaller if (!newPlugin.IsFromLocalInstallPath) { await DownloadFileAsync( - $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", + $"{Localize.DownloadingPlugin()} {newPlugin.Name}", newPlugin.UrlDownload, filePath, cts); } else @@ -80,7 +74,7 @@ public static class PluginInstaller throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath); } - if (!API.InstallPlugin(newPlugin, filePath)) + if (!PublicApi.Instance.InstallPlugin(newPlugin, filePath)) { return; } @@ -92,23 +86,20 @@ public static class PluginInstaller } catch (Exception e) { - API.LogException(ClassName, "Failed to install plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin")); + PublicApi.Instance.LogException(ClassName, "Failed to install plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorInstallingPlugin()); return; // do not restart on failure } if (Settings.AutoRestartAfterChanging) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( - API.GetTranslation("installbtn"), - string.Format( - API.GetTranslation( - "InstallSuccessNoRestart"), - newPlugin.Name)); + PublicApi.Instance.ShowMsg( + Localize.installbtn(), + Localize.InstallSuccessNoRestart(newPlugin.Name)); } } @@ -133,24 +124,23 @@ public static class PluginInstaller } catch (Exception e) { - API.LogException(ClassName, "Failed to validate zip file", e); - API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson")); + PublicApi.Instance.LogException(ClassName, "Failed to validate zip file", e); + PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson()); return; } - if (API.PluginModified(plugin.ID)) + if (PublicApi.Instance.PluginModified(plugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + Localize.pluginModifiedAlreadyMessage()); return; } if (Settings.ShowUnknownSourceWarning) { if (!InstallSourceKnown(plugin.Website) - && API.ShowMsgBox(string.Format( - API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine), - API.GetTranslation("InstallFromUnknownSourceTitle"), + && PublicApi.Instance.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine), + Localize.InstallFromUnknownSourceTitle(), MessageBoxButton.YesNo) == MessageBoxResult.No) return; } @@ -165,51 +155,46 @@ public static class PluginInstaller /// A Task representing the asynchronous uninstall operation. public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin) { - if (API.PluginModified(oldPlugin.ID)) + if (PublicApi.Instance.PluginModified(oldPlugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), oldPlugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name), + Localize.pluginModifiedAlreadyMessage()); return; } - if (API.ShowMsgBox( - string.Format( - API.GetTranslation("UninstallPromptSubtitle"), - oldPlugin.Name, oldPlugin.Author, Environment.NewLine), - API.GetTranslation("UninstallPromptTitle"), + if (PublicApi.Instance.ShowMsgBox( + Localize.UninstallPromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine), + Localize.UninstallPromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; - var removePluginSettings = API.ShowMsgBox( - API.GetTranslation("KeepPluginSettingsSubtitle"), - API.GetTranslation("KeepPluginSettingsTitle"), + var removePluginSettings = PublicApi.Instance.ShowMsgBox( + Localize.KeepPluginSettingsSubtitle(), + Localize.KeepPluginSettingsTitle(), button: MessageBoxButton.YesNo) == MessageBoxResult.No; try { - if (!await API.UninstallPluginAsync(oldPlugin, removePluginSettings)) + if (!await PublicApi.Instance.UninstallPluginAsync(oldPlugin, removePluginSettings)) { return; } } catch (Exception e) { - API.LogException(ClassName, "Failed to uninstall plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin")); + PublicApi.Instance.LogException(ClassName, "Failed to uninstall plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorUninstallingPlugin()); return; // don not restart on failure } if (Settings.AutoRestartAfterChanging) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( - API.GetTranslation("uninstallbtn"), - string.Format( - API.GetTranslation( - "UninstallSuccessNoRestart"), - oldPlugin.Name)); + PublicApi.Instance.ShowMsg( + Localize.uninstallbtn(), + Localize.UninstallSuccessNoRestart(oldPlugin.Name)); } } @@ -221,11 +206,9 @@ public static class PluginInstaller /// A Task representing the asynchronous update operation. public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin) { - if (API.ShowMsgBox( - string.Format( - API.GetTranslation("UpdatePromptSubtitle"), - oldPlugin.Name, oldPlugin.Author, Environment.NewLine), - API.GetTranslation("UpdatePromptTitle"), + if (PublicApi.Instance.ShowMsgBox( + Localize.UpdatePromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine), + Localize.UpdatePromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; try @@ -237,7 +220,7 @@ public static class PluginInstaller if (!newPlugin.IsFromLocalInstallPath) { await DownloadFileAsync( - $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", + $"{Localize.DownloadingPlugin()} {newPlugin.Name}", newPlugin.UrlDownload, filePath, cts); } else @@ -251,30 +234,27 @@ public static class PluginInstaller return; } - if (!await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath)) + if (!await PublicApi.Instance.UpdatePluginAsync(oldPlugin, newPlugin, filePath)) { return; } } catch (Exception e) { - API.LogException(ClassName, "Failed to update plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); + PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin()); return; // do not restart on failure } if (Settings.AutoRestartAfterChanging) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( - API.GetTranslation("updatebtn"), - string.Format( - API.GetTranslation( - "UpdateSuccessNoRestart"), - newPlugin.Name)); + PublicApi.Instance.ShowMsg( + Localize.updatebtn(), + Localize.UpdateSuccessNoRestart(newPlugin.Name)); } } @@ -289,17 +269,17 @@ public static class PluginInstaller public static async Task CheckForPluginUpdatesAsync(Action> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default) { // Update the plugin manifest - await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); + await PublicApi.Instance.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); // Get all plugins that can be updated var resultsForUpdate = ( - from existingPlugin in API.GetAllPlugins() - join pluginUpdateSource in API.GetPluginManifest() + from existingPlugin in PublicApi.Instance.GetAllPlugins() + join pluginUpdateSource in PublicApi.Instance.GetPluginManifest() on existingPlugin.Metadata.ID equals pluginUpdateSource.ID where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, StringComparison.InvariantCulture) < 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) - && !API.PluginModified(existingPlugin.Metadata.ID) + && !PublicApi.Instance.PluginModified(existingPlugin.Metadata.ID) select new PluginUpdateInfo() { @@ -314,25 +294,25 @@ public static class PluginInstaller }).ToList(); // No updates - if (!resultsForUpdate.Any()) + if (resultsForUpdate.Count == 0) { if (!silentUpdate) { - API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle")); + PublicApi.Instance.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle()); } return; } // If all plugins are modified, just return - if (resultsForUpdate.All(x => API.PluginModified(x.ID))) + if (resultsForUpdate.All(x => PublicApi.Instance.PluginModified(x.ID))) { return; } // Show message box with button to update all plugins - API.ShowMsgWithButton( - API.GetTranslation("updateAllPluginsTitle"), - API.GetTranslation("updateAllPluginsButtonContent"), + PublicApi.Instance.ShowMsgWithButton( + Localize.updateAllPluginsTitle(), + Localize.updateAllPluginsButtonContent(), () => { updateAllPlugins(resultsForUpdate); @@ -357,7 +337,7 @@ public static class PluginInstaller using var cts = new CancellationTokenSource(); await DownloadFileAsync( - $"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}", + $"{Localize.DownloadingPlugin()} {plugin.PluginNewUserPlugin.Name}", plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts); // check if user cancelled download before installing plugin @@ -366,7 +346,7 @@ public static class PluginInstaller return; } - if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) + if (!await PublicApi.Instance.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) { return; } @@ -375,8 +355,8 @@ public static class PluginInstaller } catch (Exception e) { - API.LogException(ClassName, "Failed to update plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); + PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin()); } })); @@ -384,13 +364,13 @@ public static class PluginInstaller if (restart) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( - API.GetTranslation("updatebtn"), - API.GetTranslation("PluginsUpdateSuccessNoRestart")); + PublicApi.Instance.ShowMsg( + Localize.updatebtn(), + Localize.PluginsUpdateSuccessNoRestart()); } } @@ -412,7 +392,7 @@ public static class PluginInstaller if (showProgress) { var exceptionHappened = false; - await API.ShowProgressBoxAsync(progressBoxTitle, + await PublicApi.Instance.ShowProgressBoxAsync(progressBoxTitle, async (reportProgress) => { if (reportProgress == null) @@ -424,18 +404,18 @@ public static class PluginInstaller } else { - await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false); + await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false); } }, cts.Cancel); // if exception happened while downloading and user does not cancel downloading, // we need to redownload the plugin if (exceptionHappened && (!cts.IsCancellationRequested)) - await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); + await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); } else { - await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); + await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); } } @@ -462,7 +442,7 @@ public static class PluginInstaller if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost) return false; - return API.GetAllPlugins().Any(x => + return PublicApi.Instance.GetAllPlugins().Any(x => !string.IsNullOrEmpty(x.Metadata.Website) && x.Metadata.Website.StartsWith(constructedUrlPart) ); diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index ab5b276b5..1d69eb587 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; @@ -29,10 +28,6 @@ namespace Flow.Launcher.Core.Plugin public static readonly HashSet GlobalPlugins = new(); public static readonly Dictionary NonGlobalPlugins = new(); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - private static PluginsSettings Settings; private static readonly ConcurrentBag ModifiedPlugins = new(); @@ -75,12 +70,12 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e); } } - API.SavePluginSettings(); - API.SavePluginCaches(); + PublicApi.Instance.SavePluginSettings(); + PublicApi.Instance.SavePluginCaches(); } public static async ValueTask DisposePluginsAsync() @@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e); } } @@ -218,7 +213,7 @@ namespace Flow.Launcher.Core.Plugin { if (string.IsNullOrEmpty(metadata.AssemblyName)) { - API.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}"); + PublicApi.Instance.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}"); continue; // Skip if AssemblyName is not set, which can happen for erroneous plugins } metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); @@ -228,7 +223,7 @@ namespace Flow.Launcher.Core.Plugin { if (string.IsNullOrEmpty(metadata.Name)) { - API.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}"); + PublicApi.Instance.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}"); continue; // Skip if Name is not set, which can happen for erroneous plugins } metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); @@ -249,28 +244,28 @@ namespace Flow.Launcher.Core.Plugin { try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", - () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API))); + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", + () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, PublicApi.Instance))); pair.Metadata.InitTime += milliseconds; - API.LogInfo(ClassName, + PublicApi.Instance.LogInfo(ClassName, $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); } catch (Exception e) { - API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e); if (pair.Metadata.Disabled && pair.Metadata.HomeDisabled) { // If this plugin is already disabled, do not show error message again // Or else it will be shown every time - API.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error"); + PublicApi.Instance.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error"); } else { pair.Metadata.Disabled = true; pair.Metadata.HomeDisabled = true; failedPlugins.Enqueue(pair); - API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); + PublicApi.Instance.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); } } })); @@ -295,15 +290,12 @@ namespace Flow.Launcher.Core.Plugin } } - if (failedPlugins.Any()) + if (!failedPlugins.IsEmpty) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); - API.ShowMsg( - API.GetTranslation("failedToInitializePluginsTitle"), - string.Format( - API.GetTranslation("failedToInitializePluginsMessage"), - failed - ), + PublicApi.Instance.ShowMsg( + Localize.failedToInitializePluginsTitle(), + Localize.failedToInitializePluginsMessage(failed), "", false ); @@ -326,7 +318,7 @@ namespace Flow.Launcher.Core.Plugin if (dialogJump && plugin.Plugin is not IAsyncDialogJump) return Array.Empty(); - if (API.PluginModified(plugin.Metadata.ID)) + if (PublicApi.Instance.PluginModified(plugin.Metadata.ID)) return Array.Empty(); return new List @@ -347,7 +339,7 @@ namespace Flow.Launcher.Core.Plugin try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -391,7 +383,7 @@ namespace Flow.Launcher.Core.Plugin try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -408,7 +400,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e); return null; } return results; @@ -421,7 +413,7 @@ namespace Flow.Launcher.Core.Plugin try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await ((IAsyncDialogJump)pair.Plugin).QueryDialogJumpAsync(query, token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -438,7 +430,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e); return null; } return results; @@ -505,7 +497,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, + PublicApi.Instance.LogException(ClassName, $"Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e); } @@ -636,8 +628,8 @@ namespace Flow.Launcher.Core.Plugin { if (PluginModified(existingVersion.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), existingVersion.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name), + Localize.pluginModifiedAlreadyMessage()); return false; } @@ -669,8 +661,8 @@ namespace Flow.Launcher.Core.Plugin { if (checkModified && PluginModified(plugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + Localize.pluginModifiedAlreadyMessage()); return false; } @@ -689,15 +681,15 @@ namespace Flow.Launcher.Core.Plugin if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) { - API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), - string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath)); + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), + Localize.fileNotFoundMessage(pluginFolderPath)); return false; } if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) { - API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), - API.GetTranslation("pluginExistAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), + Localize.pluginExistAlreadyMessage()); return false; } @@ -726,7 +718,7 @@ namespace Flow.Launcher.Core.Plugin var newPluginPath = Path.Combine(installDirectory, folderName); - FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s)); + FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s)); try { @@ -735,7 +727,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); } if (checkModified) @@ -750,8 +742,8 @@ namespace Flow.Launcher.Core.Plugin { if (checkModified && PluginModified(plugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + Localize.pluginModifiedAlreadyMessage()); return false; } @@ -770,7 +762,7 @@ namespace Flow.Launcher.Core.Plugin if (removePluginSettings) { // For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances - if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable) + if (AllowedLanguage.IsDotNet(plugin.Language) && PublicApi.Instance is IRemovable removable) { removable.RemovePluginSettings(plugin.AssemblyName); removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath); @@ -784,9 +776,9 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); - API.ShowMsgError(API.GetTranslation("failedToRemovePluginSettingsTitle"), - string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); + PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); + PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(), + Localize.failedToRemovePluginSettingsMessage(plugin.Name)); } } @@ -800,9 +792,9 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); - API.ShowMsgError(API.GetTranslation("failedToRemovePluginCacheTitle"), - string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); + PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); + PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginCacheTitle(), + Localize.failedToRemovePluginCacheMessage(plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index e9e5ee367..a8a4fba3a 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins.Environments; #pragma warning disable IDE0005 using Flow.Launcher.Infrastructure.Logger; @@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginsLoader); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); @@ -64,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin foreach (var metadata in metadatas) { - var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => + var milliseconds = PublicApi.Instance.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => { Assembly assembly = null; IAsyncPlugin plugin = null; @@ -89,19 +82,19 @@ namespace Flow.Launcher.Core.Plugin #else catch (Exception e) when (assembly == null) { - Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); } catch (InvalidOperationException e) { - Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); } catch (ReflectionTypeLoadException e) { - Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); } catch (Exception e) { - Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); } #endif @@ -121,12 +114,12 @@ namespace Flow.Launcher.Core.Plugin var errorPluginString = string.Join(Environment.NewLine, erroredPlugins); var errorMessage = erroredPlugins.Count > 1 ? - API.GetTranslation("pluginsHaveErrored") : - API.GetTranslation("pluginHasErrored"); + Localize.pluginsHaveErrored(): + Localize.pluginHasErrored(); - API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + + PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + - API.GetTranslation("referToLogs")); + Localize.referToLogs()); } return plugins; diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 983f8b234..6f373746e 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; @@ -18,10 +17,6 @@ namespace Flow.Launcher.Core.Resource { private static readonly string ClassName = nameof(Internationalization); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - private const string Folder = "Languages"; private const string DefaultLanguageCode = "en"; private const string DefaultFile = "en.xaml"; @@ -104,7 +99,7 @@ namespace Flow.Launcher.Core.Resource var directory = Path.Combine(Constant.ProgramDirectory, Folder); if (!Directory.Exists(directory)) { - API.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>"); + PublicApi.Instance.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>"); return; } @@ -175,7 +170,7 @@ namespace Flow.Launcher.Core.Resource FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase)); if (language == null) { - API.LogError(ClassName, $"Language code can't be found <{languageCode}>"); + PublicApi.Instance.LogError(ClassName, $"Language code can't be found <{languageCode}>"); return AvailableLanguages.English; } else @@ -208,7 +203,7 @@ namespace Flow.Launcher.Core.Resource } catch (Exception e) { - API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e); + PublicApi.Instance.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e); } finally { @@ -254,7 +249,7 @@ namespace Flow.Launcher.Core.Resource // "Do you want to search with pinyin?" string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?"; - if (API.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) + if (PublicApi.Instance.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) return false; return true; @@ -311,7 +306,7 @@ namespace Flow.Launcher.Core.Resource } else { - API.LogError(ClassName, $"Language path can't be found <{path}>"); + PublicApi.Instance.LogError(ClassName, $"Language path can't be found <{path}>"); var english = Path.Combine(folder, DefaultFile); if (File.Exists(english)) { @@ -319,7 +314,7 @@ namespace Flow.Launcher.Core.Resource } else { - API.LogError(ClassName, $"Default English Language path can't be found <{path}>"); + PublicApi.Instance.LogError(ClassName, $"Default English Language path can't be found <{path}>"); return string.Empty; } } @@ -354,7 +349,7 @@ namespace Flow.Launcher.Core.Resource } else { - API.LogError(ClassName, $"No Translation for key {key}"); + PublicApi.Instance.LogError(ClassName, $"No Translation for key {key}"); return $"No Translation for key {key}"; } } @@ -377,7 +372,7 @@ namespace Flow.Launcher.Core.Resource } catch (Exception e) { - API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); } } } diff --git a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs index 3e1a19a76..acd9d9eb7 100644 --- a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs +++ b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs @@ -1,15 +1,9 @@ using System.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Resource { public class LocalizedDescriptionAttribute : DescriptionAttribute { - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - private readonly string _resourceKey; public LocalizedDescriptionAttribute(string resourceKey) @@ -21,7 +15,7 @@ namespace Flow.Launcher.Core.Resource { get { - string description = API.GetTranslation(_resourceKey); + string description = PublicApi.Instance.GetTranslation(_resourceKey); return string.IsNullOrWhiteSpace(description) ? string.Format("[[{0}]]", _resourceKey) : description; } diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index a6e8dc6bf..d1f7da2a2 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -444,7 +444,7 @@ namespace Flow.Launcher.Core.Resource _api.LogError(ClassName, $"Theme <{theme}> path can't be found"); if (theme != Constant.DefaultTheme) { - _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme)); + _api.ShowMsgBox(Localize.theme_load_failure_path_not_exists(theme)); ChangeTheme(Constant.DefaultTheme); } return false; @@ -454,7 +454,7 @@ namespace Flow.Launcher.Core.Resource _api.LogError(ClassName, $"Theme <{theme}> fail to parse"); if (theme != Constant.DefaultTheme) { - _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme)); + _api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme)); ChangeTheme(Constant.DefaultTheme); } return false; diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 45275696c..1f138e843 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -41,8 +41,8 @@ namespace Flow.Launcher.Core try { if (!silentUpdate) - _api.ShowMsg(_api.GetTranslation("pleaseWait"), - _api.GetTranslation("update_flowlauncher_update_check")); + _api.ShowMsg(Localize.pleaseWait(), + Localize.update_flowlauncher_update_check()); using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false); @@ -58,13 +58,13 @@ namespace Flow.Launcher.Core if (newReleaseVersion <= currentVersion) { if (!silentUpdate) - _api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest")); + _api.ShowMsgBox(Localize.update_flowlauncher_already_on_latest()); return; } if (!silentUpdate) - _api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"), - _api.GetTranslation("update_flowlauncher_updating")); + _api.ShowMsg(Localize.update_flowlauncher_update_found(), + Localize.update_flowlauncher_updating()); await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false); @@ -77,10 +77,7 @@ namespace Flow.Launcher.Core FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)); if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s))) - _api.ShowMsgBox(string.Format( - _api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"), - DataLocation.PortableDataPath, - targetDestination)); + _api.ShowMsgBox(Localize.update_flowlauncher_fail_moving_portable_user_profile_data(DataLocation.PortableDataPath, targetDestination)); } else { @@ -91,7 +88,7 @@ namespace Flow.Launcher.Core _api.LogInfo(ClassName, $"Update success:{newVersionTips}"); - if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), + if (_api.ShowMsgBox(newVersionTips, Localize.update_flowlauncher_new_update(), MessageBoxButton.YesNo) == MessageBoxResult.Yes) { UpdateManager.RestartApp(Constant.ApplicationFileName); @@ -111,8 +108,8 @@ namespace Flow.Launcher.Core } if (!silentUpdate) - _api.ShowMsgError(_api.GetTranslation("update_flowlauncher_fail"), - _api.GetTranslation("update_flowlauncher_check_connection")); + _api.ShowMsgError(Localize.update_flowlauncher_fail(), + Localize.update_flowlauncher_check_connection()); } finally { @@ -150,9 +147,9 @@ namespace Flow.Launcher.Core return manager; } - private string NewVersionTips(string version) + private static string NewVersionTips(string version) { - var tips = string.Format(_api.GetTranslation("newVersionTips"), version); + var tips = Localize.newVersionTips(version); return tips; } diff --git a/Flow.Launcher.Core/packages.lock.json b/Flow.Launcher.Core/packages.lock.json index b499a5860..ba97f57f3 100644 --- a/Flow.Launcher.Core/packages.lock.json +++ b/Flow.Launcher.Core/packages.lock.json @@ -11,6 +11,12 @@ "YamlDotNet": "9.1.0" } }, + "Flow.Launcher.Localization": { + "type": "Direct", + "requested": "[0.0.6, )", + "resolved": "0.0.6", + "contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw==" + }, "FSharp.Core": { "type": "Direct", "requested": "[9.0.303, )", @@ -84,6 +90,11 @@ "resolved": "1.1.0", "contentHash": "j/zGAQ9hLbl7JDpeO40DaXvyyNxwQNDwnJEN7eCexn5F9Kid+VKya/Er0rfIv5Zod/32XarkqFP/V6WFHS/UpQ==" }, + "ini-parser": { + "type": "Transitive", + "resolved": "2.5.2", + "contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg==" + }, "InputSimulator": { "type": "Transitive", "resolved": "1.0.4", @@ -1161,6 +1172,7 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", + "Flow.Launcher.Localization": "[0.0.6, )", "Flow.Launcher.Plugin": "[5.0.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", @@ -1170,7 +1182,8 @@ "NLog.OutputDebugString": "[6.0.4, )", "SharpVectors.Wpf": "[1.8.5, )", "System.Drawing.Common": "[7.0.0, )", - "ToolGood.Words.Pinyin": "[3.1.0.3, )" + "ToolGood.Words.Pinyin": "[3.1.0.3, )", + "ini-parser": "[2.5.2, )" } }, "flow.launcher.plugin": { diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 65652878f..9035a541d 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -58,21 +58,17 @@ namespace Flow.Launcher.Infrastructure.DialogJump private static readonly Settings _settings = Ioc.Default.GetRequiredService(); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - private static HWND _mainWindowHandle = HWND.Null; private static readonly Dictionary _dialogJumpExplorers = new(); private static DialogJumpExplorerPair _lastExplorer = null; - private static readonly object _lastExplorerLock = new(); + private static readonly Lock _lastExplorerLock = new(); private static readonly Dictionary _dialogJumpDialogs = new(); private static IDialogJumpDialogWindow _dialogWindow = null; - private static readonly object _dialogWindowLock = new(); + private static readonly Lock _dialogWindowLock = new(); private static HWINEVENTHOOK _foregroundChangeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _locationChangeHook = HWINEVENTHOOK.Null; @@ -89,8 +85,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump private static DispatcherTimer _dragMoveTimer = null; // A list of all file dialog windows that are auto switched already - private static readonly List _autoSwitchedDialogs = new(); - private static readonly object _autoSwitchedDialogsLock = new(); + private static readonly List _autoSwitchedDialogs = []; + private static readonly Lock _autoSwitchedDialogsLock = new(); private static HWINEVENTHOOK _moveSizeHook = HWINEVENTHOOK.Null; private static readonly WINEVENTPROC _moveProc = MoveSizeCallBack; @@ -315,7 +311,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump { foreach (var explorer in _dialogJumpExplorers.Keys) { - if (API.PluginModified(explorer.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(explorer.Metadata.ID) || // Plugin is modified explorer.Metadata.Disabled) continue; // Plugin is disabled var explorerWindow = explorer.Plugin.CheckExplorerWindow(hWnd); @@ -493,7 +489,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump var dialogWindowChanged = false; foreach (var dialog in _dialogJumpDialogs.Keys) { - if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled IDialogJumpDialogWindow dialogWindow; @@ -596,7 +592,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump { foreach (var explorer in _dialogJumpExplorers.Keys) { - if (API.PluginModified(explorer.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(explorer.Metadata.ID) || // Plugin is modified explorer.Metadata.Disabled) continue; // Plugin is disabled var explorerWindow = explorer.Plugin.CheckExplorerWindow(hwnd); @@ -871,7 +867,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump // Then check all dialog windows foreach (var dialog in _dialogJumpDialogs.Keys) { - if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled var dialogWindow = _dialogJumpDialogs[dialog]; @@ -884,7 +880,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump // Finally search for the dialog window again foreach (var dialog in _dialogJumpDialogs.Keys) { - if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled IDialogJumpDialogWindow dialogWindow; @@ -1067,11 +1063,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump _navigationLock.Dispose(); // Stop drag move timer - if (_dragMoveTimer != null) - { - _dragMoveTimer.Stop(); - _dragMoveTimer = null; - } + _dragMoveTimer?.Stop(); + _dragMoveTimer = null; } #endregion diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 5b4eaf893..4cde3f6e0 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -56,10 +57,12 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -80,4 +83,15 @@ + + true + + + + + + Languages\en.xaml + + + \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 8afab419b..f8c111f36 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -4,10 +4,8 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; using JetBrains.Annotations; namespace Flow.Launcher.Infrastructure.Http @@ -20,10 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http private static readonly HttpClient client = new(); - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - static Http() { // need to be added so it would work on a win10 machine @@ -82,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http } catch (UriFormatException e) { - API.ShowMsgError(API.GetTranslation("pleaseTryAgain"), API.GetTranslation("parseProxyFailed")); + PublicApi.Instance.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed()); Log.Exception(ClassName, "Unable to parse Uri", e); } } diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs index 4ce0df026..86f757eb8 100644 --- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs +++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs @@ -1,13 +1,14 @@ using System; -using System.Runtime.InteropServices; using System.IO; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media.Imaging; +using IniParser; using Windows.Win32; using Windows.Win32.Foundation; -using Windows.Win32.UI.Shell; using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.Shell; namespace Flow.Launcher.Infrastructure.Image { @@ -35,9 +36,32 @@ namespace Flow.Launcher.Infrastructure.Image private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205; + private const string UrlExtension = ".url"; + + /// + /// Obtains a BitmapSource thumbnail for the specified file. + /// + /// + /// If the file is a Windows URL shortcut (".url"), the method attempts to resolve the shortcut's icon and use that for the thumbnail; otherwise it requests a thumbnail for the file path. The native HBITMAP used to create the BitmapSource is always released to avoid native memory leaks. + /// + /// Path to the file (can be a regular file or a ".url" shortcut). + /// Requested thumbnail width in pixels. + /// Requested thumbnail height in pixels. + /// Thumbnail extraction options (flags) controlling fallback and caching behavior. + /// A BitmapSource representing the requested thumbnail. public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options) { - HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); + HBITMAP hBitmap; + + var extension = Path.GetExtension(fileName); + if (string.Equals(extension, UrlExtension, StringComparison.OrdinalIgnoreCase)) + { + hBitmap = GetHBitmapForUrlFile(fileName, width, height, options); + } + else + { + hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); + } try { @@ -50,6 +74,21 @@ namespace Flow.Launcher.Infrastructure.Image } } + /// + /// Obtains a native HBITMAP for the specified file at the requested size using the Windows Shell image factory. + /// + /// + /// If is and thumbnail extraction fails + /// due to extraction errors or a missing path, the method falls back to requesting an icon (). + /// The returned HBITMAP is a raw GDI handle; the caller is responsible for releasing it (e.g., via DeleteObject) to avoid native memory leaks. + /// + /// Path to the file to thumbnail. + /// Requested thumbnail width in pixels. + /// Requested thumbnail height in pixels. + /// Thumbnail request flags that control behavior (e.g., ThumbnailOnly, IconOnly). + /// An HBITMAP handle containing the image. Caller must free the handle when finished. + /// If creating the shell item fails (HRESULT returned by SHCreateItemFromParsingName). + /// If the shell item does not expose IShellItemImageFactory or if an unexpected error occurs while obtaining the image. private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options) { var retCode = PInvoke.SHCreateItemFromParsingName( @@ -108,5 +147,44 @@ namespace Flow.Launcher.Infrastructure.Image return hBitmap; } + + /// + /// Obtains an HBITMAP for a Windows .url shortcut by resolving its IconFile entry and delegating to GetHBitmap. + /// + /// + /// The method parses the .url file as an INI, looks in the "InternetShortcut" section for the "IconFile" entry, + /// and requests a bitmap for that icon path. If no IconFile is present or any error occurs while reading or + /// resolving the icon, it falls back to requesting a thumbnail for the .url file itself. + /// + /// Path to the .url shortcut file. + /// Requested thumbnail width (pixels). + /// Requested thumbnail height (pixels). + /// ThumbnailOptions flags controlling extraction behavior. + /// An HBITMAP containing the requested image; callers are responsible for freeing the native handle. + private static unsafe HBITMAP GetHBitmapForUrlFile(string fileName, int width, int height, ThumbnailOptions options) + { + HBITMAP hBitmap; + + try + { + var parser = new FileIniDataParser(); + var data = parser.ReadFile(fileName); + var urlSection = data["InternetShortcut"]; + + var iconPath = urlSection?["IconFile"]; + if (!File.Exists(iconPath)) + { + // If the IconFile is missing, throw exception to fallback to the default icon + throw new FileNotFoundException("Icon file not specified in Internet shortcut (.url) file."); + } + hBitmap = GetHBitmap(Path.GetFullPath(iconPath), width, height, options); + } + catch + { + hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); + } + + return hBitmap; + } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs index 9c795f952..009b27666 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs @@ -1,18 +1,13 @@ using System.Text.Json.Serialization; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomBrowserViewModel : BaseModel { - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - public string Name { get; set; } [JsonIgnore] - public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name; + public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default() : Name; public string Path { get; set; } public string PrivateArg { get; set; } public bool EnablePrivate { get; set; } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs index 2af0bb0e5..ae406f4c5 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs @@ -1,18 +1,13 @@ using System.Text.Json.Serialization; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomExplorerViewModel : BaseModel { - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - public string Name { get; set; } [JsonIgnore] - public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name; + public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer() : Name; public string Path { get; set; } public string FileArgument { get; set; } = "\"%d\""; public string DirectoryArgument { get; set; } = "\"%d\""; diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs index 2603d4675..a2e95b668 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs @@ -1,8 +1,6 @@ using System; using System.Text.Json.Serialization; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { @@ -55,11 +53,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public string Description { get; set; } - public string LocalizedDescription => API.GetTranslation(Description); - - // We should not initialize API in static constructor because it will create another API instance - private static IPublicAPI api = null; - private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); + public string LocalizedDescription => PublicApi.Instance.GetTranslation(Description); public BaseBuiltinShortcutModel(string key, string description) { diff --git a/Flow.Launcher.Infrastructure/packages.lock.json b/Flow.Launcher.Infrastructure/packages.lock.json index 47c94d5f6..db77f9d93 100644 --- a/Flow.Launcher.Infrastructure/packages.lock.json +++ b/Flow.Launcher.Infrastructure/packages.lock.json @@ -23,12 +23,24 @@ "resolved": "8.4.0", "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" }, + "Flow.Launcher.Localization": { + "type": "Direct", + "requested": "[0.0.6, )", + "resolved": "0.0.6", + "contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw==" + }, "Fody": { "type": "Direct", "requested": "[6.9.3, )", "resolved": "6.9.3", "contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA==" }, + "ini-parser": { + "type": "Direct", + "requested": "[2.5.2, )", + "resolved": "2.5.2", + "contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg==" + }, "InputSimulator": { "type": "Direct", "requested": "[1.0.4, )", diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 6c506cfc0..3af57f00d 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -150,6 +150,16 @@ namespace Flow.Launcher.Plugin.SharedCommands return File.Exists(filePath); } + /// + /// Checks if a file or directory exists + /// + /// + /// + public static bool FileOrLocationExists(this string path) + { + return LocationExists(path) || FileExists(path); + } + /// /// Open a directory window (using the OS's default handler, usually explorer) /// diff --git a/Flow.Launcher/ActionKeywords.xaml.cs b/Flow.Launcher/ActionKeywords.xaml.cs index 8e05686c9..a94b265fc 100644 --- a/Flow.Launcher/ActionKeywords.xaml.cs +++ b/Flow.Launcher/ActionKeywords.xaml.cs @@ -47,7 +47,7 @@ namespace Flow.Launcher if (addedActionKeywords.Any(App.API.ActionKeywordAssigned)) { - App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsHasBeenAssigned")); + App.API.ShowMsgBox(Localize.newActionKeywordsHasBeenAssigned()); return; } @@ -63,7 +63,7 @@ namespace Flow.Launcher if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords)) { // User just changes the sequence of action keywords - App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsSameAsOld")); + App.API.ShowMsgBox(Localize.newActionKeywordsSameAsOld()); } else { diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 8ec11e5ff..58f8438d2 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -276,7 +276,7 @@ namespace Flow.Launcher // but if it fails (permissions, etc) then don't keep retrying // this also gives the user a visual indication in the Settings widget _settings.StartFlowLauncherOnSystemStartup = false; - API.ShowMsgError(API.GetTranslation("setAutoStartFailed"), e.Message); + API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } } diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs index 2ee08bf85..3bba2c5b8 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs @@ -41,7 +41,7 @@ namespace Flow.Launcher if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword)) { - App.API.ShowMsgBox(App.API.GetTranslation("emptyPluginHotkey")); + App.API.ShowMsgBox(Localize.emptyPluginHotkey()); return; } diff --git a/Flow.Launcher/CustomShortcutSetting.xaml.cs b/Flow.Launcher/CustomShortcutSetting.xaml.cs index f4644a267..317d059a1 100644 --- a/Flow.Launcher/CustomShortcutSetting.xaml.cs +++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs @@ -40,14 +40,14 @@ namespace Flow.Launcher { if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(Value)) { - App.API.ShowMsgBox(App.API.GetTranslation("emptyShortcut")); + App.API.ShowMsgBox(Localize.emptyShortcut()); return; } // Check if key is modified or adding a new one if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key)) { - App.API.ShowMsgBox(App.API.GetTranslation("duplicateShortcut")); + App.API.ShowMsgBox(Localize.duplicateShortcut()); return; } diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index a99d4d8c2..aa8e95429 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -37,14 +37,53 @@ prompt 4 false + $(NoWarn);FLSG0007 - + - + @@ -94,6 +133,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -123,6 +163,10 @@ + + true + + Always diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 86a68475e..bb1cddc6c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -61,8 +61,8 @@ internal static class HotKeyMapper string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}", e.Message, e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + string errorMsg = Localize.registerHotkeyFailed(hotkeyStr); + string errorMsgTitle = Localize.MessageBoxTitle(); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } @@ -87,8 +87,8 @@ internal static class HotKeyMapper e.Message, e.StackTrace, hotkeyStr)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + string errorMsg = Localize.registerHotkeyFailed(hotkeyStr); + string errorMsgTitle = Localize.MessageBoxTitle(); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } @@ -112,8 +112,8 @@ internal static class HotKeyMapper string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}", e.Message, e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + string errorMsg = Localize.unregisterHotkeyFailed(hotkeyStr); + string errorMsgTitle = Localize.MessageBoxTitle(); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 89bfde349..b920b53a7 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -234,7 +234,7 @@ namespace Flow.Launcher private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); - public string EmptyHotkey => App.API.GetTranslation("none"); + public string EmptyHotkey => Localize.none(); public ObservableCollection KeysToDisplay { get; set; } = new(); diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index c7af8c5b8..740425f8b 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -33,7 +33,7 @@ public partial class HotkeyControlDialog : ContentDialog public EResultType ResultType { get; private set; } = EResultType.Cancel; public string ResultValue { get; private set; } = string.Empty; - public static string EmptyHotkey => App.API.GetTranslation("none"); + public static string EmptyHotkey => Localize.none(); private static bool isOpenFlowHotkey; @@ -41,7 +41,7 @@ public partial class HotkeyControlDialog : ContentDialog { WindowTitle = windowTitle switch { - "" or null => App.API.GetTranslation("hotkeyRegTitle"), + "" or null => Localize.hotkeyRegTitle(), _ => windowTitle }; DefaultHotkey = defaultHotkey; @@ -146,10 +146,7 @@ public partial class HotkeyControlDialog : ContentDialog Alert.Visibility = Visibility.Visible; if (registeredHotkeyData.RemoveHotkey is not null) { - tbMsg.Text = string.Format( - App.API.GetTranslation("hotkeyUnavailableEditable"), - description - ); + tbMsg.Text = Localize.hotkeyUnavailableEditable(description); SaveBtn.IsEnabled = false; SaveBtn.Visibility = Visibility.Collapsed; OverwriteBtn.IsEnabled = true; @@ -158,10 +155,7 @@ public partial class HotkeyControlDialog : ContentDialog } else { - tbMsg.Text = string.Format( - App.API.GetTranslation("hotkeyUnavailableUneditable"), - description - ); + tbMsg.Text = Localize.hotkeyUnavailableUneditable(description); SaveBtn.IsEnabled = false; SaveBtn.Visibility = Visibility.Visible; OverwriteBtn.IsEnabled = false; @@ -175,7 +169,7 @@ public partial class HotkeyControlDialog : ContentDialog if (!CheckHotkeyAvailability(hotkey.Value, true)) { - tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); + tbMsg.Text = Localize.hotkeyUnavailable(); Alert.Visibility = Visibility.Visible; SaveBtn.IsEnabled = false; SaveBtn.Visibility = Visibility.Visible; diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 626fe1385..a51782f40 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -209,6 +209,8 @@ Version Website Uninstall + Search delay time: default + Search delay time: {0}ms Fail to remove plugin settings Plugins: {0} - Fail to remove plugin settings files, please remove them manually Fail to remove plugin cache @@ -588,8 +590,9 @@ The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. Error - An error occurred while opening the folder. {0} + An error occurred while opening the folder. An error occurred while opening the URL in the browser. Please check your Default Web Browser configuration in the General section of the settings window + File or directory not found: {0} Please wait... diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 7b6a0d79b..c4ed73a0d 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Media; @@ -145,8 +145,8 @@ namespace Flow.Launcher _settings.ReleaseNotesVersion = Constant.Version; // Show release note popup with button App.API.ShowMsgWithButton( - string.Format(App.API.GetTranslation("appUpdateTitle"), Constant.Version), - App.API.GetTranslation("appUpdateButtonContent"), + Localize.appUpdateTitle(Constant.Version), + Localize.appUpdateButtonContent(), () => { Application.Current.Dispatcher.Invoke(() => @@ -753,12 +753,12 @@ namespace Flow.Launcher private void UpdateNotifyIconText() { var menu = _contextMenu; - ((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") + + ((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")"; - ((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode"); - ((MenuItem)menu.Items[2]).Header = App.API.GetTranslation("PositionReset"); - ((MenuItem)menu.Items[3]).Header = App.API.GetTranslation("iconTraySettings"); - ((MenuItem)menu.Items[4]).Header = App.API.GetTranslation("iconTrayExit"); + ((MenuItem)menu.Items[1]).Header = Localize.GameMode(); + ((MenuItem)menu.Items[2]).Header = Localize.PositionReset(); + ((MenuItem)menu.Items[3]).Header = Localize.iconTraySettings(); + ((MenuItem)menu.Items[4]).Header = Localize.iconTrayExit(); } private void InitializeContextMenu() @@ -768,31 +768,31 @@ namespace Flow.Launcher var openIcon = new FontIcon { Glyph = "\ue71e" }; var open = new MenuItem { - Header = App.API.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", + Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")", Icon = openIcon }; var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; var gamemode = new MenuItem { - Header = App.API.GetTranslation("GameMode"), + Header = Localize.GameMode(), Icon = gamemodeIcon }; var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; var positionreset = new MenuItem { - Header = App.API.GetTranslation("PositionReset"), + Header = Localize.PositionReset(), Icon = positionresetIcon }; var settingsIcon = new FontIcon { Glyph = "\ue713" }; var settings = new MenuItem { - Header = App.API.GetTranslation("iconTraySettings"), + Header = Localize.iconTraySettings(), Icon = settingsIcon }; var exitIcon = new FontIcon { Glyph = "\ue7e8" }; var exit = new MenuItem { - Header = App.API.GetTranslation("iconTrayExit"), + Header = Localize.iconTrayExit(), Icon = exitIcon }; @@ -802,8 +802,8 @@ namespace Flow.Launcher settings.Click += (o, e) => App.API.OpenSettingDialog(); exit.Click += (o, e) => Close(); - gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip"); - positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip"); + gamemode.ToolTip = Localize.GameModeToolTip(); + positionreset.ToolTip = Localize.PositionResetToolTip(); _contextMenu.Items.Add(open); _contextMenu.Items.Add(gamemode); diff --git a/Flow.Launcher/PluginUpdateWindow.xaml.cs b/Flow.Launcher/PluginUpdateWindow.xaml.cs index 20f033425..4b56e5836 100644 --- a/Flow.Launcher/PluginUpdateWindow.xaml.cs +++ b/Flow.Launcher/PluginUpdateWindow.xaml.cs @@ -23,7 +23,7 @@ namespace Flow.Launcher { var checkBox = new CheckBox { - Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion), + Content = Localize.updatePluginCheckboxContent(plugin.Name, plugin.CurrentVersion, plugin.NewVersion), IsChecked = true, Margin = new Thickness(0, 5, 0, 5), Tag = plugin, @@ -50,10 +50,7 @@ namespace Flow.Launcher { if (sender is not CheckBox cb) return; if (cb.Tag is not PluginUpdateInfo plugin) return; - if (Plugins.Contains(plugin)) - { - Plugins.Remove(plugin); - } + Plugins.Remove(plugin); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) @@ -66,7 +63,7 @@ namespace Flow.Launcher { if (Plugins.Count == 0) { - App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected")); + App.API.ShowMsgBox(Localize.updatePluginNoSelected()); return; } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 6a8ee40f9..bd2f80743 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; @@ -74,7 +74,6 @@ namespace Flow.Launcher _mainVM.ChangeQueryText(query, requery); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")] public void RestartApp() { _mainVM.Hide(); @@ -179,20 +178,20 @@ namespace Flow.Launcher Clipboard.SetFileDropList(paths); }); - + if (exception == null) { if (showDefaultNotification) { ShowMsg( - $"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}", - GetTranslation("completedSuccessfully")); + $"{Localize.copy()} {(isFile ? Localize.fileTitle(): Localize.folderTitle())}", + Localize.completedSuccessfully()); } } else { LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception); - ShowMsgError(GetTranslation("failedToCopy")); + ShowMsgError(Localize.failedToCopy()); } } else @@ -210,15 +209,15 @@ namespace Flow.Launcher if (showDefaultNotification) { ShowMsg( - $"{GetTranslation("copy")} {GetTranslation("textTitle")}", - GetTranslation("completedSuccessfully")); + $"{Localize.copy()} {Localize.textTitle()}", + Localize.completedSuccessfully()); } } else { LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception); - ShowMsgError(GetTranslation("failedToCopy")); - } + ShowMsgError(Localize.failedToCopy()); + } } } @@ -327,7 +326,7 @@ namespace Flow.Launcher ((PluginJsonStorage)_pluginJsonStorages[type]).Save(); } - + public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null) { try @@ -394,24 +393,30 @@ namespace Flow.Launcher } catch (Win32Exception ex) when (ex.NativeErrorCode == 2) { - LogError(ClassName, "File Manager not found"); + LogException(ClassName, "File Manager not found", ex); ShowMsgError( - GetTranslation("fileManagerNotFoundTitle"), - string.Format(GetTranslation("fileManagerNotFound"), ex.Message) + Localize.fileManagerNotFoundTitle(), + Localize.fileManagerNotFound() ); } catch (Exception ex) { LogException(ClassName, "Failed to open folder", ex); ShowMsgError( - GetTranslation("errorTitle"), - string.Format(GetTranslation("folderOpenError"), ex.Message) + Localize.errorTitle(), + Localize.folderOpenError() ); } } private void OpenUri(Uri uri, bool? inPrivate = null, bool forceBrowser = false) { + if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath)) + { + ShowMsgError(Localize.errorTitle(), Localize.fileNotFoundError(uri.LocalPath)); + return; + } + if (forceBrowser || uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) { var browserInfo = _settings.CustomBrowser; @@ -434,20 +439,26 @@ namespace Flow.Launcher var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window"; LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e); ShowMsgError( - GetTranslation("errorTitle"), - GetTranslation("browserOpenError") + Localize.errorTitle(), + Localize.browserOpenError() ); } } else { - Process.Start(new ProcessStartInfo() + try { - FileName = uri.AbsoluteUri, - UseShellExecute = true - })?.Dispose(); - - return; + Process.Start(new ProcessStartInfo() + { + FileName = uri.AbsoluteUri, + UseShellExecute = true + })?.Dispose(); + } + catch (Exception e) + { + LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e); + ShowMsgError(Localize.errorTitle(), e.Message); + } } } @@ -481,7 +492,7 @@ namespace Flow.Launcher OpenUri(appUri); } - public void ToggleGameMode() + public void ToggleGameMode() { _mainVM.ToggleGameMode(); } diff --git a/Flow.Launcher/ReleaseNotesWindow.xaml.cs b/Flow.Launcher/ReleaseNotesWindow.xaml.cs index ce7a3e084..4e3f30d30 100644 --- a/Flow.Launcher/ReleaseNotesWindow.xaml.cs +++ b/Flow.Launcher/ReleaseNotesWindow.xaml.cs @@ -132,8 +132,8 @@ namespace Flow.Launcher RefreshButton.Visibility = Visibility.Visible; MarkdownViewer.Visibility = Visibility.Collapsed; App.API.ShowMsgError( - App.API.GetTranslation("checkNetworkConnectionTitle"), - App.API.GetTranslation("checkNetworkConnectionSubTitle")); + Localize.checkNetworkConnectionTitle(), + Localize.checkNetworkConnectionSubTitle()); } else { diff --git a/Flow.Launcher/ReportWindow.xaml.cs b/Flow.Launcher/ReportWindow.xaml.cs index ae0767934..bb0ce0073 100644 --- a/Flow.Launcher/ReportWindow.xaml.cs +++ b/Flow.Launcher/ReportWindow.xaml.cs @@ -48,10 +48,10 @@ namespace Flow.Launcher _ => Constant.IssuesUrl }; - var paragraph = Hyperlink(App.API.GetTranslation("reportWindow_please_open_issue"), websiteUrl); - paragraph.Inlines.Add(string.Format(App.API.GetTranslation("reportWindow_upload_log"), log.FullName)); + var paragraph = Hyperlink(Localize.reportWindow_please_open_issue(), websiteUrl); + paragraph.Inlines.Add(Localize.reportWindow_upload_log(log.FullName)); paragraph.Inlines.Add("\n"); - paragraph.Inlines.Add(App.API.GetTranslation("reportWindow_copy_below")); + paragraph.Inlines.Add(Localize.reportWindow_copy_below()); ErrorTextbox.Document.Blocks.Add(paragraph); StringBuilder content = new StringBuilder(); diff --git a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs index 10cd18821..5e3ab6815 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs @@ -59,7 +59,7 @@ namespace Flow.Launcher.Resources.Pages } catch (Exception e) { - App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); + App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index 647b36701..f906bf55c 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -25,7 +25,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel get { var size = GetLogFiles().Sum(file => file.Length); - return $"{App.API.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})"; + return $"{Localize.clearlogfolder()} ({BytesToReadableString(size)})"; } } @@ -34,7 +34,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel get { var size = GetCacheFiles().Sum(file => file.Length); - return $"{App.API.GetTranslation("clearcachefolder")} ({BytesToReadableString(size)})"; + return $"{Localize.clearcachefolder()} ({BytesToReadableString(size)})"; } } @@ -51,10 +51,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel _ => Constant.Version }; - public string ActivatedTimes => string.Format( - App.API.GetTranslation("about_activate_times"), - _settings.ActivateTimes - ); + public string ActivatedTimes => Localize.about_activate_times(_settings.ActivateTimes); public class LogLevelData : DropdownDataGeneric { } @@ -98,8 +95,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel private void AskClearLogFolderConfirmation() { var confirmResult = App.API.ShowMsgBox( - App.API.GetTranslation("clearlogfolderMessage"), - App.API.GetTranslation("clearlogfolder"), + Localize.clearlogfolderMessage(), + Localize.clearlogfolder(), MessageBoxButton.YesNo ); @@ -107,7 +104,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel { if (!ClearLogFolder()) { - App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); + App.API.ShowMsgBox(Localize.clearfolderfailMessage()); } } } @@ -116,8 +113,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel private void AskClearCacheFolderConfirmation() { var confirmResult = App.API.ShowMsgBox( - App.API.GetTranslation("clearcachefolderMessage"), - App.API.GetTranslation("clearcachefolder"), + Localize.clearcachefolderMessage(), + Localize.clearcachefolder(), MessageBoxButton.YesNo ); @@ -125,7 +122,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel { if (!ClearCacheFolder()) { - App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); + App.API.ShowMsgBox(Localize.clearfolderfailMessage()); } } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index 885330b8c..6641ac689 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -65,7 +65,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel } catch (Exception e) { - App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); + App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } } @@ -92,7 +92,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel } catch (Exception e) { - App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); + App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } } @@ -257,7 +257,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel else { // Since this is rarely seen text, language support is not provided. - App.API.ShowMsgError(App.API.GetTranslation("KoreanImeSettingChangeFailTitle"), App.API.GetTranslation("KoreanImeSettingChangeFailSubTitle")); + App.API.ShowMsgError(Localize.KoreanImeSettingChangeFailTitle(), Localize.KoreanImeSettingChangeFailSubTitle()); } } } @@ -325,10 +325,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel public List Languages => _translater.LoadAvailableLanguages(); - public string AlwaysPreviewToolTip => string.Format( - App.API.GetTranslation("AlwaysPreviewToolTip"), - Settings.PreviewHotkey - ); + public string AlwaysPreviewToolTip => Localize.AlwaysPreviewToolTip(Settings.PreviewHotkey); private static string GetFileFromDialog(string title, string filter = "") { @@ -372,7 +369,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel private void SelectPython() { var selectedFile = GetFileFromDialog( - App.API.GetTranslation("selectPythonExecutable"), + Localize.selectPythonExecutable(), "Python|pythonw.exe" ); @@ -384,7 +381,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel private void SelectNode() { var selectedFile = GetFileFromDialog( - App.API.GetTranslation("selectNodeExecutable"), + Localize.selectNodeExecutable(), "node|*.exe" ); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 9e6a31dc7..3e7c3cb83 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -50,15 +50,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomPluginHotkey; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } var result = App.API.ShowMsgBox( - string.Format( - App.API.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey - ), - App.API.GetTranslation("delete"), + Localize.deleteCustomHotkeyWarning(item.Hotkey), + Localize.delete(), MessageBoxButton.YesNo ); @@ -75,7 +73,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomPluginHotkey; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } @@ -83,7 +81,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); if (settingItem == null) { - App.API.ShowMsgBox(App.API.GetTranslation("invalidPluginHotkey")); + App.API.ShowMsgBox(Localize.invalidPluginHotkey()); return; } @@ -114,15 +112,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomShortcut; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } var result = App.API.ShowMsgBox( - string.Format( - App.API.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value - ), - App.API.GetTranslation("delete"), + Localize.deleteCustomShortcutWarning(item.Key, item.Value), + Localize.delete(), MessageBoxButton.YesNo ); @@ -138,7 +134,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomShortcut; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } @@ -146,7 +142,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel o.Key == item.Key && o.Value == item.Value); if (settingItem == null) { - App.API.ShowMsgBox(App.API.GetTranslation("invalidShortcut")); + App.API.ShowMsgBox(Localize.invalidShortcut()); return; } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index f133b7d2b..d67695a75 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -103,8 +103,8 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel private async Task InstallPluginAsync() { var file = GetFileFromDialog( - App.API.GetTranslation("SelectZipFile"), - $"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip"); + Localize.SelectZipFile(), + $"{Localize.ZipFiles()} (*.zip)|*.zip"); if (!string.IsNullOrEmpty(file)) await PluginInstaller.InstallPluginAndCheckRestartAsync(file); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 98dac499f..70bcbcc18 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -26,7 +26,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel private readonly Theme _theme; private readonly string DefaultFont = Win32Helper.GetSystemDefaultFont(); - public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : ""; + public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? Localize.BackdropTypeDisabledToolTip(): ""; public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; @@ -272,7 +272,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel public string PlaceholderTextTip { - get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder")); + get => Localize.PlaceholderTextTip(Localize.queryTextBoxPlaceholder()); } public string PlaceholderText @@ -447,8 +447,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel { new() { - Title = App.API.GetTranslation("SampleTitleExplorer"), - SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"), + Title = Localize.SampleTitleExplorer(), + SubTitle = Localize.SampleSubTitleExplorer(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" @@ -456,8 +456,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel }, new() { - Title = App.API.GetTranslation("SampleTitleWebSearch"), - SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"), + Title = Localize.SampleTitleWebSearch(), + SubTitle = Localize.SampleSubTitleWebSearch(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" @@ -465,8 +465,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel }, new() { - Title = App.API.GetTranslation("SampleTitleProgram"), - SubTitle = App.API.GetTranslation("SampleSubTitleProgram"), + Title = Localize.SampleTitleProgram(), + SubTitle = Localize.SampleSubTitleProgram(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" @@ -474,8 +474,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel }, new() { - Title = App.API.GetTranslation("SampleTitleProcessKiller"), - SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"), + Title = Localize.SampleTitleProcessKiller(), + SubTitle = Localize.SampleSubTitleProcessKiller(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d492f28c5..66fa70682 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -342,8 +342,8 @@ namespace Flow.Launcher.ViewModel Hide(); await PluginManager.ReloadDataAsync().ConfigureAwait(false); - App.API.ShowMsg(App.API.GetTranslation("success"), - App.API.GetTranslation("completedSuccessfully")); + App.API.ShowMsg(Localize.success(), + Localize.completedSuccessfully()); } [RelayCommand] @@ -908,7 +908,7 @@ namespace Flow.Launcher.ViewModel private string _placeholderText; public string PlaceholderText { - get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText; + get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText; set { _placeholderText = value; @@ -1312,12 +1312,10 @@ namespace Flow.Launcher.ViewModel var results = new List(); foreach (var h in historyItems) { - var title = App.API.GetTranslation("executeQuery"); - var time = App.API.GetTranslation("lastExecuteTime"); var result = new Result { - Title = string.Format(title, h.Query), - SubTitle = string.Format(time, h.ExecutedDateTime), + Title = Localize.executeQuery(h.Query), + SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), IcoPath = Constant.HistoryIcon, OriginQuery = new Query { RawQuery = h.Query }, Action = _ => @@ -1714,13 +1712,13 @@ namespace Flow.Launcher.ViewModel { menu = new Result { - Title = App.API.GetTranslation("cancelTopMostInThisQuery"), + Title = Localize.cancelTopMostInThisQuery(), IcoPath = "Images\\down.png", PluginDirectory = Constant.ProgramDirectory, Action = _ => { _topMostRecord.Remove(result); - App.API.ShowMsg(App.API.GetTranslation("success")); + App.API.ShowMsg(Localize.success()); App.API.ReQuery(); return false; }, @@ -1732,13 +1730,13 @@ namespace Flow.Launcher.ViewModel { menu = new Result { - Title = App.API.GetTranslation("setAsTopMostInThisQuery"), + Title = Localize.setAsTopMostInThisQuery(), IcoPath = "Images\\up.png", PluginDirectory = Constant.ProgramDirectory, Action = _ => { _topMostRecord.AddOrUpdate(result); - App.API.ShowMsg(App.API.GetTranslation("success")); + App.API.ShowMsg(Localize.success()); App.API.ReQuery(); return false; }, @@ -1756,10 +1754,10 @@ namespace Flow.Launcher.ViewModel var metadata = PluginManager.GetPluginForId(id).Metadata; var translator = App.API; - var author = translator.GetTranslation("author"); - var website = translator.GetTranslation("website"); - var version = translator.GetTranslation("version"); - var plugin = translator.GetTranslation("plugin"); + var author = Localize.author(); + var website = Localize.website(); + var version = Localize.version(); + var plugin = Localize.plugin(); var title = $"{plugin}: {metadata.Name}"; var icon = metadata.IcoPath; var subtitle = $"{author} {metadata.Author}"; diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 29f2b9b43..bf7720651 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -155,8 +155,7 @@ namespace Flow.Launcher.ViewModel App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e); // Show error message in UI - var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"), - pair.Metadata.Name, Environment.NewLine, e.Message); + var errorMsg = Localize.errorCreatingSettingPanel(pair.Metadata.Name, Environment.NewLine, e.Message); return CreateErrorSettingPanel(errorMsg); } } @@ -165,16 +164,16 @@ namespace Flow.Launcher.ViewModel Visibility.Collapsed : Visibility.Visible; public string InitializeTime => PluginPair.Metadata.InitTime + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; - public string Version => App.API.GetTranslation("plugin_query_version") + " " + PluginPair.Metadata.Version; + public string Version => Localize.plugin_query_version() + " " + PluginPair.Metadata.Version; public string InitAndQueryTime => - App.API.GetTranslation("plugin_init_time") + " " + + Localize.plugin_init_time() + " " + PluginPair.Metadata.InitTime + "ms, " + - App.API.GetTranslation("plugin_query_time") + " " + + Localize.plugin_query_time() + " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ? - App.API.GetTranslation("default") : - App.API.GetTranslation($"SearchDelayTime{PluginPair.Metadata.SearchDelayTime}"); + Localize.plugin_default_search_delay_time() : + Localize.plugin_search_delay_time(PluginPair.Metadata.SearchDelayTime); public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; } public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay; public string DefaultSearchDelay => Settings.SearchDelayTime.ToString(); diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs index e3a0e4e44..04602dcae 100644 --- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs +++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs @@ -50,7 +50,7 @@ public partial class SelectBrowserViewModel : BaseModel { CustomBrowsers.Add(new() { - Name = App.API.GetTranslation("defaultBrowser_new_profile") + Name = Localize.defaultBrowser_new_profile() }); SelectedCustomBrowserIndex = CustomBrowsers.Count - 1; } diff --git a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs index f6a32e3fe..42c818042 100644 --- a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs +++ b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs @@ -48,9 +48,8 @@ public partial class SelectFileManagerViewModel : BaseModel if (!IsFileManagerValid(CustomExplorer.Path)) { var result = App.API.ShowMsgBox( - string.Format(App.API.GetTranslation("fileManagerPathNotFound"), - CustomExplorer.Name, CustomExplorer.Path), - App.API.GetTranslation("fileManagerPathError"), + Localize.fileManagerPathNotFound(CustomExplorer.Name, CustomExplorer.Path), + Localize.fileManagerPathError(), MessageBoxButton.YesNo, MessageBoxImage.Warning); @@ -105,7 +104,7 @@ public partial class SelectFileManagerViewModel : BaseModel { CustomExplorers.Add(new() { - Name = App.API.GetTranslation("defaultBrowser_new_profile") + Name = Localize.defaultBrowser_new_profile() }); SelectedCustomExplorerIndex = CustomExplorers.Count - 1; } diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index 2c685ea09..16d4f27c2 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -14,6 +14,12 @@ "resolved": "8.4.0", "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" }, + "Flow.Launcher.Localization": { + "type": "Direct", + "requested": "[0.0.6, )", + "resolved": "0.0.6", + "contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw==" + }, "Fody": { "type": "Direct", "requested": "[6.9.3, )", @@ -204,6 +210,11 @@ "resolved": "1.11.42", "contentHash": "LDc1bEfF14EY2DZzak4xvzWvbpNXK3vi1u0KQbBpLUN4+cx/VrvXhgCAMSJhSU5vz0oMfW9JZIR20vj/PkDHPA==" }, + "ini-parser": { + "type": "Transitive", + "resolved": "2.5.2", + "contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg==" + }, "InputSimulator": { "type": "Transitive", "resolved": "1.0.4", @@ -1599,6 +1610,7 @@ "Droplex": "[1.7.0, )", "FSharp.Core": "[9.0.303, )", "Flow.Launcher.Infrastructure": "[1.0.0, )", + "Flow.Launcher.Localization": "[0.0.6, )", "Flow.Launcher.Plugin": "[5.0.0, )", "Meziantou.Framework.Win32.Jobs": "[3.4.5, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", @@ -1613,6 +1625,7 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", + "Flow.Launcher.Localization": "[0.0.6, )", "Flow.Launcher.Plugin": "[5.0.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", @@ -1622,7 +1635,8 @@ "NLog.OutputDebugString": "[6.0.4, )", "SharpVectors.Wpf": "[1.8.5, )", "System.Drawing.Common": "[7.0.0, )", - "ToolGood.Words.Pinyin": "[3.1.0.3, )" + "ToolGood.Words.Pinyin": "[3.1.0.3, )", + "ini-parser": "[2.5.2, )" } }, "flow.launcher.plugin": { diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 8a9162e62..8392a0dbe 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -106,8 +106,8 @@ - - + + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs index b3f5a8b4b..895515caa 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs @@ -7,10 +7,10 @@ namespace Flow.Launcher.Plugin.Calculator { [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_use_system_locale))] UseSystemLocale, - + [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_dot))] - Dot, - + Dot, + [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_comma))] Comma } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 9d5e4700f..a20a1ad5d 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -5,9 +5,9 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Windows.Controls; -using Mages.Core; -using Flow.Launcher.Plugin.Calculator.Views; using Flow.Launcher.Plugin.Calculator.ViewModels; +using Flow.Launcher.Plugin.Calculator.Views; +using Mages.Core; namespace Flow.Launcher.Plugin.Calculator { @@ -26,7 +26,7 @@ namespace Flow.Launcher.Plugin.Calculator private const string IcoPath = "Images/calculator.png"; private static readonly List EmptyResults = []; - internal static PluginInitContext Context { get; set; } = null!; + internal static PluginInitContext Context { get; private set; } = null!; private Settings _settings; private SettingsViewModel _viewModel; @@ -57,10 +57,10 @@ namespace Flow.Launcher.Plugin.Calculator { var search = query.Search; bool isFunctionPresent = FunctionRegex.IsMatch(search); - + // Mages is case sensitive, so we need to convert all function names to lower case. search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); - + var decimalSep = GetDecimalSeparator(); var groupSep = GetGroupSeparator(decimalSep); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); @@ -292,7 +292,7 @@ namespace Flow.Launcher.Plugin.Calculator { processedStr = processedStr.Replace(decimalSep, "."); } - + return processedStr; } else @@ -310,7 +310,7 @@ namespace Flow.Launcher.Plugin.Calculator return processedStr; } } - + private static bool IsValidGrouping(string[] parts, int[] groupSizes) { if (parts.Length <= 1) return true; @@ -326,7 +326,7 @@ namespace Flow.Launcher.Plugin.Calculator var lastGroupSize = groupSizes.Last(); var canRepeatLastGroup = lastGroupSize != 0; - + int groupIndex = 0; for (int i = parts.Length - 1; i > 0; i--) { @@ -335,7 +335,7 @@ namespace Flow.Launcher.Plugin.Calculator { expectedSize = groupSizes[groupIndex]; } - else if(canRepeatLastGroup) + else if (canRepeatLastGroup) { expectedSize = lastGroupSize; } @@ -345,7 +345,7 @@ namespace Flow.Launcher.Plugin.Calculator } if (parts[i].Length != expectedSize) return false; - + groupIndex++; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index cac0f3080..1544dc41f 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -1,5 +1,4 @@ - -namespace Flow.Launcher.Plugin.Calculator; +namespace Flow.Launcher.Plugin.Calculator; public class Settings { diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index d8db0abe1..9002a3a4a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -32,6 +32,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -54,5 +55,9 @@ PreserveNewest + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs index 48717816b..503d82cc3 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs @@ -5,19 +5,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator { public class Main : IPlugin, IPluginI18n, IHomeQuery { - internal PluginInitContext Context { get; private set; } + internal static PluginInitContext Context { get; private set; } + + public void Init(PluginInitContext context) + { + Context = context; + } public List Query(Query query) { return QueryResults(query); } - public List HomeQuery() - { - return QueryResults(); - } - - private List QueryResults(Query query = null) + private static List QueryResults(Query query = null) { var nonGlobalPlugins = GetNonGlobalPlugins(); var querySearch = query?.Search ?? string.Empty; @@ -34,7 +34,7 @@ namespace Flow.Launcher.Plugin.PluginIndicator select new Result { Title = keyword, - SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_result_subtitle"), plugin.Name), + SubTitle = Localize.flowlauncher_plugin_pluginindicator_result_subtitle(plugin.Name), Score = score, IcoPath = plugin.IcoPath, AutoCompleteText = $"{keyword}{Plugin.Query.TermSeparator}", @@ -44,10 +44,10 @@ namespace Flow.Launcher.Plugin.PluginIndicator return false; } }; - return results.ToList(); + return [.. results]; } - private Dictionary GetNonGlobalPlugins() + private static Dictionary GetNonGlobalPlugins() { var nonGlobalPlugins = new Dictionary(); foreach (var plugin in Context.API.GetAllPlugins()) @@ -66,19 +66,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator return nonGlobalPlugins; } - public void Init(PluginInitContext context) - { - Context = context; - } - public string GetTranslatedPluginTitle() { - return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_name"); + return Localize.flowlauncher_plugin_pluginindicator_plugin_name(); } public string GetTranslatedPluginDescription() { - return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_description"); + return Localize.flowlauncher_plugin_pluginindicator_plugin_description(); + } + + public List HomeQuery() + { + return QueryResults(); } } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 0a7a02a45..39586771f 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -35,6 +35,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -52,6 +53,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs index 8f5ba4bd2..44746fa62 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs @@ -9,19 +9,19 @@ namespace Flow.Launcher.Plugin.ProcessKiller { public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider { + internal static PluginInitContext Context { get; private set; } + + private Settings _settings; + private readonly ProcessHelper processHelper = new(); - private static PluginInitContext _context; - - internal Settings Settings; - private SettingsViewModel _viewModel; public void Init(PluginInitContext context) { - _context = context; - Settings = context.API.LoadSettingJsonStorage(); - _viewModel = new SettingsViewModel(Settings); + Context = context; + _settings = context.API.LoadSettingJsonStorage(); + _viewModel = new SettingsViewModel(_settings); } public List Query(Query query) @@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller public string GetTranslatedPluginTitle() { - return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_name"); + return Localize.flowlauncher_plugin_processkiller_plugin_name(); } public string GetTranslatedPluginDescription() { - return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_description"); + return Localize.flowlauncher_plugin_processkiller_plugin_description(); } public List LoadContextMenus(Result result) @@ -51,13 +51,13 @@ namespace Flow.Launcher.Plugin.ProcessKiller { menuOptions.Add(new Result { - Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"), + Title = Localize.flowlauncher_plugin_processkiller_kill_instances(), SubTitle = processPath, Action = _ => { foreach (var p in similarProcesses) { - processHelper.TryKill(_context, p); + ProcessHelper.TryKill(p); } return true; @@ -72,8 +72,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller private List CreateResultsFromQuery(Query query) { // Get all non-system processes - var allPocessList = processHelper.GetMatchingProcesses(); - if (!allPocessList.Any()) + var allProcessList = processHelper.GetMatchingProcesses(); + if (allProcessList.Count == 0) { return null; } @@ -82,12 +82,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller var searchTerm = query.Search; var processlist = new List(); var processWindowTitle = - Settings.ShowWindowTitle || Settings.PutVisibleWindowProcessesTop ? + _settings.ShowWindowTitle || _settings.PutVisibleWindowProcessesTop ? ProcessHelper.GetProcessesWithNonEmptyWindowTitle() : - new Dictionary(); + []; if (string.IsNullOrWhiteSpace(searchTerm)) { - foreach (var p in allPocessList) + foreach (var p in allProcessList) { var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p); @@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller // Use window title for those processes if enabled processlist.Add(new ProcessResult( p, - Settings.PutVisibleWindowProcessesTop ? 200 : 0, - Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, + _settings.PutVisibleWindowProcessesTop ? 200 : 0, + _settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, null, progressNameIdTitle)); } @@ -115,35 +115,35 @@ namespace Flow.Launcher.Plugin.ProcessKiller } else { - foreach (var p in allPocessList) + foreach (var p in allProcessList) { var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p); if (processWindowTitle.TryGetValue(p.Id, out var windowTitle)) { // Get max score from searching process name, window title and process id - var windowTitleMatch = _context.API.FuzzySearch(searchTerm, windowTitle); - var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle); + var windowTitleMatch = Context.API.FuzzySearch(searchTerm, windowTitle); + var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle); var score = Math.Max(windowTitleMatch.Score, processNameIdMatch.Score); if (score > 0) { // Add score to prioritize processes with visible windows // Use window title for those processes - if (Settings.PutVisibleWindowProcessesTop) + if (_settings.PutVisibleWindowProcessesTop) { score += 200; } processlist.Add(new ProcessResult( p, score, - Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, + _settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, score == windowTitleMatch.Score ? windowTitleMatch : null, progressNameIdTitle)); } } else { - var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle); + var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle); var score = processNameIdMatch.Score; if (score > 0) { @@ -162,7 +162,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller foreach (var pr in processlist) { var p = pr.Process; - var path = processHelper.TryGetProcessFilename(p); + var path = ProcessHelper.TryGetProcessFilename(p); results.Add(new Result() { IcoPath = path, @@ -172,12 +172,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller TitleHighlightData = pr.TitleMatch?.MatchData, Score = pr.Score, ContextData = p.ProcessName, - AutoCompleteText = $"{_context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}", + AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}", Action = (c) => { - processHelper.TryKill(_context, p); + ProcessHelper.TryKill(p); // Re-query to refresh process list - _context.API.ReQuery(); + Context.API.ReQuery(); return true; } }); @@ -194,17 +194,17 @@ namespace Flow.Launcher.Plugin.ProcessKiller sortedResults.Insert(1, new Result() { IcoPath = firstResult?.IcoPath, - Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData), - SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count), + Title = Localize.flowlauncher_plugin_processkiller_kill_all(firstResult?.ContextData), + SubTitle = Localize.flowlauncher_plugin_processkiller_kill_all_count(processlist.Count), Score = 200, Action = (c) => { foreach (var p in processlist) { - processHelper.TryKill(_context, p.Process); + ProcessHelper.TryKill(p.Process); } // Re-query to refresh process list - _context.API.ReQuery(); + Context.API.ReQuery(); return true; } }); diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index cea34f7dc..0e2f78f87 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -16,8 +16,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller { private static readonly string ClassName = nameof(ProcessHelper); - private readonly HashSet _systemProcessList = new() - { + private readonly HashSet _systemProcessList = + [ "conhost", "svchost", "idle", @@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller "winlogon", "services", "spoolsv", - "explorer" - }; + "explorer" + ]; private const string FlowLauncherProcessName = "Flow.Launcher"; - private bool IsSystemProcessOrFlowLauncher(Process p) => + private bool IsSystemProcessOrFlowLauncher(Process p) => _systemProcessList.Contains(p.ProcessName.ToLower()) || string.Equals(p.ProcessName, FlowLauncherProcessName, StringComparison.OrdinalIgnoreCase); @@ -142,7 +142,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller return Process.GetProcesses().Where(p => !IsSystemProcessOrFlowLauncher(p) && TryGetProcessFilename(p) == processPath); } - public void TryKill(PluginInitContext context, Process p) + public static void TryKill(Process p) { try { @@ -154,11 +154,11 @@ namespace Flow.Launcher.Plugin.ProcessKiller } catch (Exception e) { - context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e); + Main.Context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e); } } - public unsafe string TryGetProcessFilename(Process p) + public static unsafe string TryGetProcessFilename(Process p) { try { diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs index 146c9c92c..10a1ebe4a 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs @@ -3,25 +3,16 @@ using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.ProcessKiller { - internal class ProcessResult + internal class ProcessResult(Process process, int score, string title, MatchResult match, string tooltip) { - public ProcessResult(Process process, int score, string title, MatchResult match, string tooltip) - { - Process = process; - Score = score; - Title = title; - TitleMatch = match; - Tooltip = tooltip; - } + public Process Process { get; } = process; - public Process Process { get; } + public int Score { get; } = score; - public int Score { get; } + public string Title { get; } = title; - public string Title { get; } + public MatchResult TitleMatch { get; } = match; - public MatchResult TitleMatch { get; } - - public string Tooltip { get; } + public string Tooltip { get; } = tooltip; } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs index 0728d9c0f..02690b9e5 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs @@ -1,24 +1,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller.ViewModels { - public class SettingsViewModel + public class SettingsViewModel(Settings settings) { - public Settings Settings { get; set; } - - public SettingsViewModel(Settings settings) - { - Settings = settings; - } - - public bool ShowWindowTitle - { - get => Settings.ShowWindowTitle; - set => Settings.ShowWindowTitle = value; - } - - public bool PutVisibleWindowProcessesTop - { - get => Settings.PutVisibleWindowProcessesTop; - set => Settings.PutVisibleWindowProcessesTop = value; - } + public Settings Settings { get; set; } = settings; } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml index b969be4e8..761570aff 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml @@ -4,6 +4,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vm="clr-namespace:Flow.Launcher.Plugin.ProcessKiller.ViewModels" + d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}" d:DesignHeight="300" d:DesignWidth="500" mc:Ignorable="d"> @@ -18,11 +20,11 @@ Grid.Row="0" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Content="{DynamicResource flowlauncher_plugin_processkiller_show_window_title}" - IsChecked="{Binding ShowWindowTitle}" /> + IsChecked="{Binding Settings.ShowWindowTitle}" /> + IsChecked="{Binding Settings.PutVisibleWindowProcessesTop}" /> \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs index a066ab6a9..7e712da61 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs @@ -5,9 +5,6 @@ namespace Flow.Launcher.Plugin.ProcessKiller.Views; public partial class SettingsControl : UserControl { - /// - /// Interaction logic for SettingsControl.xaml - /// public SettingsControl(SettingsViewModel viewModel) { InitializeComponent(); diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 5c3475133..e6932709f 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -58,6 +59,7 @@ + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index a86b96800..2440facd0 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -1,13 +1,13 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; +using Flow.Launcher.Plugin.SharedCommands; using WindowsInput; using WindowsInput.Native; -using Flow.Launcher.Plugin.SharedCommands; using Control = System.Windows.Controls.Control; using Keys = System.Windows.Forms.Keys; @@ -17,7 +17,7 @@ namespace Flow.Launcher.Plugin.Shell { private static readonly string ClassName = nameof(Main); - internal PluginInitContext Context { get; private set; } + internal static PluginInitContext Context { get; private set; } private const string Image = "Images/shell.png"; private bool _winRStroked; @@ -27,7 +27,7 @@ namespace Flow.Launcher.Plugin.Shell public List Query(Query query) { - List results = new List(); + List results = []; string cmd = query.Search; if (string.IsNullOrEmpty(cmd)) { @@ -45,7 +45,7 @@ namespace Flow.Launcher.Plugin.Shell string basedir = null; string dir = null; string excmd = Environment.ExpandEnvironmentVariables(cmd); - if (Directory.Exists(excmd) && (cmd.EndsWith("/") || cmd.EndsWith(@"\"))) + if (Directory.Exists(excmd) && (cmd.EndsWith('/') || cmd.EndsWith('\\'))) { basedir = excmd; dir = cmd; @@ -54,7 +54,7 @@ namespace Flow.Launcher.Plugin.Shell { basedir = Path.GetDirectoryName(excmd); var dirName = Path.GetDirectoryName(cmd); - dir = (dirName.EndsWith("/") || dirName.EndsWith(@"\")) ? dirName : cmd[..(dirName.Length + 1)]; + dir = (dirName.EndsWith('/') || dirName.EndsWith('\\')) ? dirName : cmd[..(dirName.Length + 1)]; } if (basedir != null) @@ -103,14 +103,14 @@ namespace Flow.Launcher.Plugin.Shell { if (m.Key == cmd) { - result.SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value); + result.SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value); return null; } var ret = new Result { Title = m.Key, - SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value), IcoPath = Image, Action = c => { @@ -129,9 +129,9 @@ namespace Flow.Launcher.Plugin.Shell }).Where(o => o != null); if (_settings.ShowOnlyMostUsedCMDs) - return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList(); + return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)]; - return history.ToList(); + return [.. history]; } private Result GetCurrentCmd(string cmd) @@ -140,7 +140,7 @@ namespace Flow.Launcher.Plugin.Shell { Title = cmd, Score = 5000, - SubTitle = Context.API.GetTranslation("flowlauncher_plugin_cmd_execute_through_shell"), + SubTitle = Localize.flowlauncher_plugin_cmd_execute_through_shell(), IcoPath = Image, Action = c => { @@ -165,7 +165,7 @@ namespace Flow.Launcher.Plugin.Shell .Select(m => new Result { Title = m.Key, - SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value), IcoPath = Image, Action = c => { @@ -182,9 +182,9 @@ namespace Flow.Launcher.Plugin.Shell }); if (_settings.ShowOnlyMostUsedCMDs) - return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList(); + return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)]; - return history.ToList(); + return [.. history]; } private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) @@ -199,7 +199,7 @@ namespace Flow.Launcher.Plugin.Shell Verb = runAsAdministratorArg, WorkingDirectory = workingDirectory, }; - var notifyStr = Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close"); + var notifyStr = Localize.flowlauncher_plugin_cmd_press_any_key_to_close(); var addedCharacter = _settings.UseWindowsTerminal ? "\\" : ""; switch (_settings.Shell) { @@ -288,10 +288,10 @@ namespace Flow.Launcher.Plugin.Shell case Shell.RunCommand: { - var parts = command.Split(new[] - { + var parts = command.Split( + [ ' ' - }, 2); + ], 2); if (parts.Length == 2) { var filename = parts[0]; @@ -336,12 +336,12 @@ namespace Flow.Launcher.Plugin.Shell catch (FileNotFoundException e) { Context.API.ShowMsgError(GetTranslatedPluginTitle(), - string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_command_not_found"), e.Message)); + Localize.flowlauncher_plugin_cmd_command_not_found(e.Message)); } catch (Win32Exception e) { Context.API.ShowMsgError(GetTranslatedPluginTitle(), - string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_error_running_command"), e.Message)); + Localize.flowlauncher_plugin_cmd_error_running_command(e.Message)); } catch (Exception e) { @@ -405,7 +405,7 @@ namespace Flow.Launcher.Plugin.Shell return true; } - private void OnWinRPressed() + private static void OnWinRPressed() { Context.API.ShowMainWindow(); // show the main window and set focus to the query box @@ -428,12 +428,12 @@ namespace Flow.Launcher.Plugin.Shell public string GetTranslatedPluginTitle() { - return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_name"); + return Localize.flowlauncher_plugin_cmd_plugin_name(); } public string GetTranslatedPluginDescription() { - return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_description"); + return Localize.flowlauncher_plugin_cmd_plugin_description(); } public List LoadContextMenus(Result selectedResult) @@ -442,7 +442,7 @@ namespace Flow.Launcher.Plugin.Shell { new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_different_user"), + Title = Localize.flowlauncher_plugin_cmd_run_as_different_user(), Action = c => { Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title)); @@ -453,7 +453,7 @@ namespace Flow.Launcher.Plugin.Shell }, new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"), + Title = Localize.flowlauncher_plugin_cmd_run_as_administrator(), Action = c => { Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true)); @@ -464,7 +464,7 @@ namespace Flow.Launcher.Plugin.Shell }, new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_copy"), + Title = Localize.flowlauncher_plugin_cmd_copy(), Action = c => { Context.API.CopyToClipboard(selectedResult.Title); diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 9ce2293a2..4616a18ec 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -5,11 +5,11 @@ namespace Flow.Launcher.Plugin.Shell public class Settings { public Shell Shell { get; set; } = Shell.Cmd; - + public bool ReplaceWinR { get; set; } = false; public bool CloseShellAfterPress { get; set; } = false; - + public bool LeaveShellOpen { get; set; } public bool RunAsAdministrator { get; set; } = true; @@ -20,18 +20,14 @@ namespace Flow.Launcher.Plugin.Shell public int ShowOnlyMostUsedCMDsNumber { get; set; } - public Dictionary CommandHistory { get; set; } = new Dictionary(); + public Dictionary CommandHistory { get; set; } = []; public void AddCmdHistory(string cmdName) { - if (CommandHistory.ContainsKey(cmdName)) + if (!CommandHistory.TryAdd(cmdName, 1)) { CommandHistory[cmdName] += 1; } - else - { - CommandHistory.Add(cmdName, 1); - } } } diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs index d87c6c7bf..0abc823e0 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs @@ -19,18 +19,18 @@ namespace Flow.Launcher.Plugin.Shell ReplaceWinR.IsChecked = _settings.ReplaceWinR; CloseShellAfterPress.IsChecked = _settings.CloseShellAfterPress; - + LeaveShellOpen.IsChecked = _settings.LeaveShellOpen; - + AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator; UseWindowsTerminal.IsChecked = _settings.UseWindowsTerminal; - + LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; - + ShowOnlyMostUsedCMDs.IsChecked = _settings.ShowOnlyMostUsedCMDs; - - if ((bool)!ShowOnlyMostUsedCMDs.IsChecked) + + if (ShowOnlyMostUsedCMDs.IsChecked != true) ShowOnlyMostUsedCMDsNumber.IsEnabled = false; ShowOnlyMostUsedCMDsNumber.ItemsSource = new List() { 5, 10, 20 }; @@ -137,7 +137,6 @@ namespace Flow.Launcher.Plugin.Shell { _settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem; }; - } } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs index 8797bf220..d0669252d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs @@ -5,15 +5,13 @@ namespace Flow.Launcher.Plugin.Sys public partial class CommandKeywordSettingWindow { private readonly Command _oldSearchSource; - private readonly PluginInitContext _context; - public CommandKeywordSettingWindow(PluginInitContext context, Command old) + public CommandKeywordSettingWindow(Command old) { - _context = context; _oldSearchSource = old; InitializeComponent(); CommandKeyword.Text = old.Keyword; - CommandKeywordTips.Text = string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_custom_command_keyword_tip"), old.Name); + CommandKeywordTips.Text = Localize.flowlauncher_plugin_sys_custom_command_keyword_tip(old.Name); } private void OnCancelButtonClick(object sender, RoutedEventArgs e) @@ -26,8 +24,8 @@ namespace Flow.Launcher.Plugin.Sys var keyword = CommandKeyword.Text; if (string.IsNullOrEmpty(keyword)) { - var warning = _context.API.GetTranslation("flowlauncher_plugin_sys_input_command_keyword"); - _context.API.ShowMsgBox(warning); + var warning = Localize.flowlauncher_plugin_sys_input_command_keyword(); + Main.Context.API.ShowMsgBox(warning); } else { diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index 44fc9a8cf..4cf09baab 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -58,6 +59,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml index 56899eef3..9e9a2f93d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml @@ -78,4 +78,9 @@ System Commands Provides System related commands. e.g. shutdown, lock, settings etc. + + This theme supports two (light/dark) modes and Blur Transparent Background + This theme supports two (light/dark) modes + This theme supports Blur Transparent Background + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 77278a054..89067d44c 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using System.Windows; using Windows.Win32; using Windows.Win32.Foundation; @@ -42,7 +43,7 @@ namespace Flow.Launcher.Plugin.Sys {"Toggle Game Mode", "flowlauncher_plugin_sys_toggle_game_mode_cmd"}, {"Set Flow Launcher Theme", "flowlauncher_plugin_sys_theme_selector_cmd"} }; - private readonly Dictionary KeywordDescriptionMappings = new(); + private readonly Dictionary KeywordDescriptionMappings = []; // SHTDN_REASON_MAJOR_OTHER indicates a generic shutdown reason that isn't categorized under hardware failure, // software updates, or other predefined reasons. @@ -52,22 +53,21 @@ namespace Flow.Launcher.Plugin.Sys private const string Documentation = "https://flowlauncher.com/docs/#/usage-tips"; - private PluginInitContext _context; + internal static PluginInitContext Context { get; private set; } private Settings _settings; - private ThemeSelector _themeSelector; private SettingsViewModel _viewModel; public Control CreateSettingPanel() { UpdateLocalizedNameDescription(false); - return new SysSettings(_context, _viewModel); + return new SysSettings(_viewModel); } public List Query(Query query) { - if(query.Search.StartsWith(ThemeSelector.Keyword)) + if (query.Search.StartsWith(ThemeSelector.Keyword)) { - return _themeSelector.Query(query); + return ThemeSelector.Query(query); } var commands = Commands(query); @@ -85,9 +85,9 @@ namespace Flow.Launcher.Plugin.Sys } // Match from localized title & localized subtitle & keyword - var titleMatch = _context.API.FuzzySearch(query.Search, c.Title); - var subTitleMatch = _context.API.FuzzySearch(query.Search, c.SubTitle); - var keywordMatch = _context.API.FuzzySearch(query.Search, command.Keyword); + var titleMatch = Context.API.FuzzySearch(query.Search, c.Title); + var subTitleMatch = Context.API.FuzzySearch(query.Search, c.SubTitle); + var keywordMatch = Context.API.FuzzySearch(query.Search, command.Keyword); // Get the largest score from them var score = Math.Max(titleMatch.Score, subTitleMatch.Score); @@ -113,30 +113,29 @@ namespace Flow.Launcher.Plugin.Sys { if (!KeywordTitleMappings.TryGetValue(key, out var translationKey)) { - _context.API.LogError(ClassName, $"Title not found for: {key}"); + Context.API.LogError(ClassName, $"Title not found for: {key}"); return "Title Not Found"; } - return _context.API.GetTranslation(translationKey); + return Context.API.GetTranslation(translationKey); } private string GetDescription(string key) { if (!KeywordDescriptionMappings.TryGetValue(key, out var translationKey)) { - _context.API.LogError(ClassName, $"Description not found for: {key}"); + Context.API.LogError(ClassName, $"Description not found for: {key}"); return "Description Not Found"; } - return _context.API.GetTranslation(translationKey); + return Context.API.GetTranslation(translationKey); } public void Init(PluginInitContext context) { - _context = context; + Context = context; _settings = context.API.LoadSettingJsonStorage(); _viewModel = new SettingsViewModel(_settings); - _themeSelector = new ThemeSelector(context); foreach (string key in KeywordTitleMappings.Keys) { // Remove _cmd in the last of the strings @@ -194,12 +193,12 @@ namespace Flow.Launcher.Plugin.Sys } } - private List Commands(Query query) + private static List Commands(Query query) { var results = new List(); var recycleBinFolder = "shell:RecycleBinFolder"; - results.AddRange(new[] - { + results.AddRange( + [ new Result { Title = "Shutdown", @@ -207,9 +206,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\shutdown.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"), - _context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_shutdown_computer(), + Localize.flowlauncher_plugin_sys_shutdown_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -228,9 +227,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\restart.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"), - _context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_restart_computer(), + Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -249,9 +248,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\restart_advanced.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"), - _context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_restart_computer_advanced(), + Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -270,9 +269,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\logoff.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"), - _context.API.GetTranslation("flowlauncher_plugin_sys_log_off"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_logoff_computer(), + Localize.flowlauncher_plugin_sys_log_off(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -338,9 +337,9 @@ namespace Flow.Launcher.Plugin.Sys var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0); if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED) { - _context.API.ShowMsgBox( - string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed"), Environment.NewLine), - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_error"), + Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed(Environment.NewLine), + Localize.flowlauncher_plugin_sys_dlgtitle_error(), MessageBoxButton.OK, MessageBoxImage.Error); } @@ -366,7 +365,7 @@ namespace Flow.Launcher.Plugin.Sys Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"), Action = c => { - _context.API.HideMainWindow(); + Context.API.HideMainWindow(); Application.Current.MainWindow.Close(); return true; } @@ -378,9 +377,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\app.png", Action = c => { - _context.API.SaveAppAllSettings(); - _context.API.ShowMsg(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_settings_saved")); + Context.API.SaveAppAllSettings(); + Context.API.ShowMsg(Localize.flowlauncher_plugin_sys_dlgtitle_success(), + Localize.flowlauncher_plugin_sys_dlgtext_all_settings_saved()); return true; } }, @@ -391,7 +390,7 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\app.png", Action = c => { - _context.API.RestartApp(); + Context.API.RestartApp(); return false; } }, @@ -403,8 +402,8 @@ namespace Flow.Launcher.Plugin.Sys Action = c => { // Hide the window first then open setting dialog because main window can be topmost window which will still display on top of the setting dialog for a while - _context.API.HideMainWindow(); - _context.API.OpenSettingDialog(); + Context.API.HideMainWindow(); + Context.API.OpenSettingDialog(); return true; } }, @@ -416,14 +415,13 @@ namespace Flow.Launcher.Plugin.Sys Action = c => { // Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen. - _context.API.HideMainWindow(); + Context.API.HideMainWindow(); - _ = _context.API.ReloadAllPluginData().ContinueWith(_ => - _context.API.ShowMsg( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), - _context.API.GetTranslation( - "flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")), - System.Threading.Tasks.TaskScheduler.Current); + _ = Context.API.ReloadAllPluginData().ContinueWith(_ => + Context.API.ShowMsg( + Localize.flowlauncher_plugin_sys_dlgtitle_success(), + Localize.flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded()), + TaskScheduler.Current); return true; } @@ -435,8 +433,8 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\checkupdate.png", Action = c => { - _context.API.HideMainWindow(); - _context.API.CheckForNewUpdate(); + Context.API.HideMainWindow(); + Context.API.CheckForNewUpdate(); return true; } }, @@ -445,11 +443,11 @@ namespace Flow.Launcher.Plugin.Sys Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), Title = "Open Log Location", IcoPath = "Images\\app.png", - CopyText = _context.API.GetLogDirectory(), - AutoCompleteText = _context.API.GetLogDirectory(), + CopyText = Context.API.GetLogDirectory(), + AutoCompleteText = Context.API.GetLogDirectory(), Action = c => { - _context.API.OpenDirectory(_context.API.GetLogDirectory()); + Context.API.OpenDirectory(Context.API.GetLogDirectory()); return true; } }, @@ -462,7 +460,7 @@ namespace Flow.Launcher.Plugin.Sys AutoCompleteText = Documentation, Action = c => { - _context.API.OpenUrl(Documentation); + Context.API.OpenUrl(Documentation); return true; } }, @@ -471,11 +469,11 @@ namespace Flow.Launcher.Plugin.Sys Title = "Flow Launcher UserData Folder", Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), IcoPath = "Images\\app.png", - CopyText = _context.API.GetDataDirectory(), - AutoCompleteText = _context.API.GetDataDirectory(), + CopyText = Context.API.GetDataDirectory(), + AutoCompleteText = Context.API.GetDataDirectory(), Action = c => { - _context.API.OpenDirectory(_context.API.GetDataDirectory()); + Context.API.OpenDirectory(Context.API.GetDataDirectory()); return true; } }, @@ -486,7 +484,7 @@ namespace Flow.Launcher.Plugin.Sys Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"), Action = c => { - _context.API.ToggleGameMode(); + Context.API.ToggleGameMode(); return true; } }, @@ -499,29 +497,29 @@ namespace Flow.Launcher.Plugin.Sys { if (string.IsNullOrEmpty(query.ActionKeyword)) { - _context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); + Context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); } else { - _context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); + Context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); } return false; } } - }); + ]); return results; } public string GetTranslatedPluginTitle() { - return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_name"); + return Localize.flowlauncher_plugin_sys_plugin_name(); } public string GetTranslatedPluginDescription() { - return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_description"); + return Localize.flowlauncher_plugin_sys_plugin_description(); } public void OnCultureInfoChanged(CultureInfo _) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs b/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs index f39e6d65f..96a545e74 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs @@ -13,8 +13,8 @@ public class Settings : BaseModel } } - public ObservableCollection Commands { get; set; } = new ObservableCollection - { + public ObservableCollection Commands { get; set; } = + [ new() { Key = "Shutdown", @@ -120,7 +120,7 @@ public class Settings : BaseModel Key = "Set Flow Launcher Theme", Keyword = "Set Flow Launcher Theme" } - }; + ]; [JsonIgnore] public Command SelectedCommand { get; set; } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs index 0755dffa9..bda8c6c04 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs @@ -1,12 +1,7 @@ namespace Flow.Launcher.Plugin.Sys { - public class SettingsViewModel + public class SettingsViewModel(Settings settings) { - public SettingsViewModel(Settings settings) - { - Settings = settings; - } - - public Settings Settings { get; } + public Settings Settings { get; } = settings; } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs index 1a8621eeb..9906db46d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs @@ -1,17 +1,16 @@ using System.Windows; using System.Windows.Controls; +using System.Windows.Input; namespace Flow.Launcher.Plugin.Sys { public partial class SysSettings : UserControl { - private readonly PluginInitContext _context; private readonly Settings _settings; - public SysSettings(PluginInitContext context, SettingsViewModel viewModel) + public SysSettings(SettingsViewModel viewModel) { InitializeComponent(); - _context = context; _settings = viewModel.Settings; DataContext = viewModel; } @@ -37,15 +36,15 @@ namespace Flow.Launcher.Plugin.Sys public void OnEditCommandKeywordClick(object sender, RoutedEventArgs e) { - var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand); + var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand); commandKeyword.ShowDialog(); } - private void MouseDoubleClickItem(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void MouseDoubleClickItem(object sender, MouseButtonEventArgs e) { if (((FrameworkElement)e.OriginalSource).DataContext is Command && _settings.SelectedCommand != null) { - var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand); + var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand); commandKeyword.ShowDialog(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index f8aeaeafd..50b1063ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -4,40 +4,30 @@ using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.Sys { - public class ThemeSelector + public static class ThemeSelector { public const string Keyword = "fltheme"; - private readonly PluginInitContext _context; - - public ThemeSelector(PluginInitContext context) + public static List Query(Query query) { - _context = context; - } - - public List Query(Query query) - { - var themes = _context.API.GetAvailableThemes(); - var selectedTheme = _context.API.GetCurrentTheme(); + var themes = Main.Context.API.GetAvailableThemes(); + var selectedTheme = Main.Context.API.GetCurrentTheme(); var search = query.SecondToEndSearch; if (string.IsNullOrWhiteSpace(search)) { - return themes.Select(x => CreateThemeResult(x, selectedTheme)) - .OrderBy(x => x.Title) - .ToList(); + return [.. themes.Select(x => CreateThemeResult(x, selectedTheme)).OrderBy(x => x.Title)]; } - return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) - .Where(x => x.matchResult.IsSearchPrecisionScoreMet()) - .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) - .OrderBy(x => x.Title) - .ToList(); + return [.. themes.Select(theme => (theme, matchResult: Main.Context.API.FuzzySearch(search, theme.Name))) + .Where(x => x.matchResult.IsSearchPrecisionScoreMet()) + .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) + .OrderBy(x => x.Title)]; } - private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); + private static Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); - private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) + private static Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) { string title; if (theme == selectedTheme) @@ -53,17 +43,28 @@ namespace Flow.Launcher.Plugin.Sys score = 1000; } - string description = string.Empty; + string description; if (theme.IsDark == true) { - description += _context.API.GetTranslation("TypeIsDarkToolTip"); + if (theme.HasBlur == true) + { + description = Localize.flowlauncher_plugin_sys_type_isdark_hasblur(); + } + else + { + description = Localize.flowlauncher_plugin_sys_type_isdark(); + } } - - if (theme.HasBlur == true) + else { - if (!string.IsNullOrEmpty(description)) - description += " "; - description += _context.API.GetTranslation("TypeHasBlurToolTip"); + if (theme.HasBlur == true) + { + description = Localize.flowlauncher_plugin_sys_type_hasblur(); + } + else + { + description = string.Empty; + } } return new Result @@ -76,9 +77,9 @@ namespace Flow.Launcher.Plugin.Sys Score = score, Action = c => { - if (_context.API.SetCurrentTheme(theme)) + if (Main.Context.API.SetCurrentTheme(theme)) { - _context.API.ReQuery(); + Main.Context.API.ReQuery(); } return false; } diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index fdfe03224..091248cfd 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -33,6 +33,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -56,4 +57,8 @@ + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Url/Main.cs b/Plugins/Flow.Launcher.Plugin.Url/Main.cs index 9fa52c8da..db7cecbde 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Url/Main.cs @@ -40,7 +40,7 @@ namespace Flow.Launcher.Plugin.Url "(?:/\\S*)?" + "$"; Regex reg = new Regex(urlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); - private PluginInitContext context; + internal static PluginInitContext Context { get; private set; } private Settings _settings; public List Query(Query query) @@ -53,7 +53,7 @@ namespace Flow.Launcher.Plugin.Url new Result { Title = raw, - SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_url_open_url"),raw), + SubTitle = Localize.flowlauncher_plugin_url_open_url(raw), IcoPath = "Images/url.png", Score = 8, Action = _ => @@ -64,13 +64,13 @@ namespace Flow.Launcher.Plugin.Url } try { - context.API.OpenUrl(raw); + Context.API.OpenUrl(raw); return true; } catch(Exception) { - context.API.ShowMsgError(string.Format(context.API.GetTranslation("flowlauncher_plugin_url_cannot_open_url"), raw)); + Context.API.ShowMsgError(Localize.flowlauncher_plugin_url_cannot_open_url(raw)); return false; } } @@ -99,19 +99,19 @@ namespace Flow.Launcher.Plugin.Url public void Init(PluginInitContext context) { - this.context = context; + Context = context; _settings = context.API.LoadSettingJsonStorage(); } public string GetTranslatedPluginTitle() { - return context.API.GetTranslation("flowlauncher_plugin_url_plugin_name"); + return Localize.flowlauncher_plugin_url_plugin_name(); } public string GetTranslatedPluginDescription() { - return context.API.GetTranslation("flowlauncher_plugin_url_plugin_description"); + return Localize.flowlauncher_plugin_url_plugin_description(); } } }