Merge pull request #4011 from Flow-Launcher/flow_launcher_localization1

Use Flow.Launcher.Localization to improve code quality
This commit is contained in:
Jack Ye 2025-09-28 00:04:14 +08:00 committed by GitHub
commit 2d00ab1d3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 417 additions and 456 deletions

View file

@ -3,10 +3,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.Win32; using Microsoft.Win32;
using Squirrel; using Squirrel;
@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration
{ {
private static readonly string ClassName = nameof(Portable); private static readonly string ClassName = nameof(Portable);
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary> /// <summary>
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish /// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
/// </summary> /// </summary>
@ -45,13 +41,13 @@ namespace Flow.Launcher.Core.Configuration
#endif #endif
IndicateDeletion(DataLocation.PortableDataPath); IndicateDeletion(DataLocation.PortableDataPath);
API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode")); PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName); UpdateManager.RestartApp(Constant.ApplicationFileName);
} }
catch (Exception e) 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 #endif
IndicateDeletion(DataLocation.RoamingDataPath); IndicateDeletion(DataLocation.RoamingDataPath);
API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode")); PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName); UpdateManager.RestartApp(Constant.ApplicationFileName);
} }
catch (Exception e) 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) 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); VerifyUserDataAfterMove(fromLocation, toLocation);
} }
public void VerifyUserDataAfterMove(string fromLocation, string 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() public void CreateShortcuts()
@ -150,12 +146,12 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location // delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath)) 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) 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); Environment.Exit(0);
} }
@ -164,9 +160,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it. // delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath)) 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) if (roamingLocationExists && portableLocationExists)
{ {
API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"), PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
return false; return false;
} }

View file

@ -8,7 +8,6 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins
{ {
private static readonly string ClassName = nameof(CommunityPluginSource); 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<IPublicAPI>();
private string latestEtag = ""; private string latestEtag = "";
private List<UserPlugin> plugins = new(); private List<UserPlugin> plugins = [];
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new() private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{ {
@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
/// </remarks> /// </remarks>
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token) public async Task<List<UserPlugin>> 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); var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);
@ -59,40 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins
.ConfigureAwait(false); .ConfigureAwait(false);
latestEtag = response.Headers.ETag?.Tag; 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; return plugins;
} }
else if (response.StatusCode == HttpStatusCode.NotModified) 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; return plugins;
} }
else 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; return null;
} }
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) 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; return null;
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
// Likely an HttpClient timeout or external cancellation not requested by our token // 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; return null;
} }
catch (Exception e) catch (Exception e)
{ {
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) 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 else
{ {
API.LogException(ClassName, "Error Occurred", e); PublicApi.Instance.LogException(ClassName, "Error Occurred", e);
} }
return null; return null;
} }

View file

@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
@ -15,7 +14,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
private static readonly string ClassName = nameof(AbstractPluginEnvironment); private static readonly string ClassName = nameof(AbstractPluginEnvironment);
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>(); protected readonly IPublicAPI API = PublicApi.Instance;
internal abstract string Language { get; } internal abstract string Language { get; }
@ -58,15 +57,10 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
return SetPathForPluginPairs(PluginsSettingsFilePath, Language); return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
} }
var noRuntimeMessage = string.Format( var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine);
API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
Language,
EnvName,
Environment.NewLine
);
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) 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); 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 // Nothing selected because user pressed cancel from the file dialog window
else else
{ {
var forceDownloadMessage = string.Format( var forceDownloadMessage = Localize.runtimeExecutableInvalidChooseDownload(Language, EnvName, Environment.NewLine);
API.GetTranslation("runtimeExecutableInvalidChooseDownload"),
Language,
EnvName,
Environment.NewLine
);
// Let users select valid path or choose to download // Let users select valid path or choose to download
while (string.IsNullOrEmpty(selectedFile)) while (string.IsNullOrEmpty(selectedFile))
@ -120,7 +109,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
else else
{ {
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language)); API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language));
API.LogError(ClassName, API.LogError(ClassName,
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment"); $"{Language}Environment");
@ -248,7 +237,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
private static string GetUpdatedEnvironmentPath(string filePath) private static string GetUpdatedEnvironmentPath(string filePath)
{ {
var index = filePath.IndexOf(DataLocation.PluginEnvironments); var index = filePath.IndexOf(DataLocation.PluginEnvironments);
// get the substring after "Environments" because we can not determine it dynamically // get the substring after "Environments" because we can not determine it dynamically
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..]; var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}"; return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";

View file

@ -51,7 +51,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
catch (System.Exception e) catch (System.Exception e)
{ {
API.ShowMsgError(API.GetTranslation("failToInstallPythonEnv")); API.ShowMsgError(Localize.failToInstallPythonEnv());
API.LogException(ClassName, "Failed to install Python environment", e); API.LogException(ClassName, "Failed to install Python environment", e);
} }
}); });

View file

@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
catch (System.Exception e) catch (System.Exception e)
{ {
API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e); API.LogException(ClassName, "Failed to install TypeScript environment", e);
} }
}); });

View file

@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
catch (System.Exception e) catch (System.Exception e)
{ {
API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e); API.LogException(ClassName, "Failed to install TypeScript environment", e);
} }
}); });

View file

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
@ -23,10 +22,6 @@ namespace Flow.Launcher.Core.ExternalPlugins
private static DateTime lastFetchedAt = DateTime.MinValue; private static DateTime lastFetchedAt = DateTime.MinValue;
private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2); 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<IPublicAPI>();
public static List<UserPlugin> UserPlugins { get; private set; } public static List<UserPlugin> UserPlugins { get; private set; }
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
@ -61,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Http request failed", e); PublicApi.Instance.LogException(ClassName, "Http request failed", e);
} }
finally finally
{ {
@ -83,12 +78,12 @@ namespace Flow.Launcher.Core.ExternalPlugins
} }
catch (Exception e) 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); + "Plugin excluded from manifest", e);
return false; 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."); + $"but current version is {Constant.Version}. Plugin excluded from manifest.");
return false; return false;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework> <TargetFramework>net9.0-windows</TargetFramework>
@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -55,6 +56,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Droplex" Version="1.7.0" /> <PackageReference Include="Droplex" Version="1.7.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="FSharp.Core" Version="9.0.303" /> <PackageReference Include="FSharp.Core" Version="9.0.303" />
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.4" /> <PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.4" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
@ -62,6 +64,17 @@
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" /> <PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
<PackageReference Include="StreamJsonRpc" Version="2.22.11" /> <PackageReference Include="StreamJsonRpc" Version="2.22.11" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="Languages\en.xaml" />
<AdditionalFiles Include="..\Flow.Launcher\Languages\en.xaml">
<Link>Languages\en.xaml</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" /> <ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />

View file

@ -285,7 +285,7 @@ namespace Flow.Launcher.Core.Plugin
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin, Margin = SettingPanelItemLeftMargin,
Content = API.GetTranslation("select") Content = Localize.select()
}; };
Btn.Click += (_, _) => Btn.Click += (_, _) =>

View file

@ -5,7 +5,6 @@ using System.IO;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Text.Json; using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -13,10 +12,6 @@ namespace Flow.Launcher.Core.Plugin
{ {
private static readonly string ClassName = nameof(PluginConfig); 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<IPublicAPI>();
/// <summary> /// <summary>
/// Parse plugin metadata in the given directories /// Parse plugin metadata in the given directories
/// </summary> /// </summary>
@ -38,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Can't delete <{directory}>", e); PublicApi.Instance.LogException(ClassName, $"Can't delete <{directory}>", e);
} }
} }
else else
@ -55,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
duplicateList duplicateList
.ForEach( .ForEach(
x => API.LogWarn(ClassName, x => PublicApi.Instance.LogWarn(ClassName,
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " + string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
"not loaded due to version not the highest of the duplicates", "not loaded due to version not the highest of the duplicates",
x.Name, x.ID, x.Version), x.Name, x.ID, x.Version),
@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName); string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath)) 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; return null;
} }
@ -123,19 +118,19 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Invalid json for config <{configPath}>", e); PublicApi.Instance.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
return null; return null;
} }
if (!AllowedLanguage.IsAllowed(metadata.Language)) 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; return null;
} }
if (!File.Exists(metadata.ExecuteFilePath)) 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; return null;
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@ -22,10 +22,6 @@ public static class PluginInstaller
private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>(); private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>();
// 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<IPublicAPI>();
/// <summary> /// <summary>
/// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed. /// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed.
/// </summary> /// </summary>
@ -33,18 +29,16 @@ public static class PluginInstaller
/// <returns>A Task representing the asynchronous install operation.</returns> /// <returns>A Task representing the asynchronous install operation.</returns>
public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin) 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), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return; return;
} }
if (API.ShowMsgBox( if (PublicApi.Instance.ShowMsgBox(
string.Format( Localize.InstallPromptSubtitle(newPlugin.Name, newPlugin.Author, Environment.NewLine),
API.GetTranslation("InstallPromptSubtitle"), Localize.InstallPromptTitle(),
newPlugin.Name, newPlugin.Author, Environment.NewLine),
API.GetTranslation("InstallPromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try try
@ -61,7 +55,7 @@ public static class PluginInstaller
if (!newPlugin.IsFromLocalInstallPath) if (!newPlugin.IsFromLocalInstallPath)
{ {
await DownloadFileAsync( await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", $"{Localize.DownloadingPlugin()} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts); newPlugin.UrlDownload, filePath, cts);
} }
else else
@ -80,7 +74,7 @@ public static class PluginInstaller
throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath); 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; return;
} }
@ -92,23 +86,20 @@ public static class PluginInstaller
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to install plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to install plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorInstallingPlugin());
return; // do not restart on failure return; // do not restart on failure
} }
if (Settings.AutoRestartAfterChanging) if (Settings.AutoRestartAfterChanging)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("installbtn"), Localize.installbtn(),
string.Format( Localize.InstallSuccessNoRestart(newPlugin.Name));
API.GetTranslation(
"InstallSuccessNoRestart"),
newPlugin.Name));
} }
} }
@ -133,24 +124,23 @@ public static class PluginInstaller
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to validate zip file", e); PublicApi.Instance.LogException(ClassName, "Failed to validate zip file", e);
API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson")); PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson());
return; return;
} }
if (API.PluginModified(plugin.ID)) if (PublicApi.Instance.PluginModified(plugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return; return;
} }
if (Settings.ShowUnknownSourceWarning) if (Settings.ShowUnknownSourceWarning)
{ {
if (!InstallSourceKnown(plugin.Website) if (!InstallSourceKnown(plugin.Website)
&& API.ShowMsgBox(string.Format( && PublicApi.Instance.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine),
API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine), Localize.InstallFromUnknownSourceTitle(),
API.GetTranslation("InstallFromUnknownSourceTitle"),
MessageBoxButton.YesNo) == MessageBoxResult.No) MessageBoxButton.YesNo) == MessageBoxResult.No)
return; return;
} }
@ -165,51 +155,46 @@ public static class PluginInstaller
/// <returns>A Task representing the asynchronous uninstall operation.</returns> /// <returns>A Task representing the asynchronous uninstall operation.</returns>
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin) 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), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return; return;
} }
if (API.ShowMsgBox( if (PublicApi.Instance.ShowMsgBox(
string.Format( Localize.UninstallPromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UninstallPromptSubtitle"), Localize.UninstallPromptTitle(),
oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UninstallPromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
var removePluginSettings = API.ShowMsgBox( var removePluginSettings = PublicApi.Instance.ShowMsgBox(
API.GetTranslation("KeepPluginSettingsSubtitle"), Localize.KeepPluginSettingsSubtitle(),
API.GetTranslation("KeepPluginSettingsTitle"), Localize.KeepPluginSettingsTitle(),
button: MessageBoxButton.YesNo) == MessageBoxResult.No; button: MessageBoxButton.YesNo) == MessageBoxResult.No;
try try
{ {
if (!await API.UninstallPluginAsync(oldPlugin, removePluginSettings)) if (!await PublicApi.Instance.UninstallPluginAsync(oldPlugin, removePluginSettings))
{ {
return; return;
} }
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to uninstall plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to uninstall plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorUninstallingPlugin());
return; // don not restart on failure return; // don not restart on failure
} }
if (Settings.AutoRestartAfterChanging) if (Settings.AutoRestartAfterChanging)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("uninstallbtn"), Localize.uninstallbtn(),
string.Format( Localize.UninstallSuccessNoRestart(oldPlugin.Name));
API.GetTranslation(
"UninstallSuccessNoRestart"),
oldPlugin.Name));
} }
} }
@ -221,11 +206,9 @@ public static class PluginInstaller
/// <returns>A Task representing the asynchronous update operation.</returns> /// <returns>A Task representing the asynchronous update operation.</returns>
public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin) public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
{ {
if (API.ShowMsgBox( if (PublicApi.Instance.ShowMsgBox(
string.Format( Localize.UpdatePromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UpdatePromptSubtitle"), Localize.UpdatePromptTitle(),
oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UpdatePromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try try
@ -237,7 +220,7 @@ public static class PluginInstaller
if (!newPlugin.IsFromLocalInstallPath) if (!newPlugin.IsFromLocalInstallPath)
{ {
await DownloadFileAsync( await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", $"{Localize.DownloadingPlugin()} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts); newPlugin.UrlDownload, filePath, cts);
} }
else else
@ -251,30 +234,27 @@ public static class PluginInstaller
return; return;
} }
if (!await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath)) if (!await PublicApi.Instance.UpdatePluginAsync(oldPlugin, newPlugin, filePath))
{ {
return; return;
} }
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to update plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
return; // do not restart on failure return; // do not restart on failure
} }
if (Settings.AutoRestartAfterChanging) if (Settings.AutoRestartAfterChanging)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("updatebtn"), Localize.updatebtn(),
string.Format( Localize.UpdateSuccessNoRestart(newPlugin.Name));
API.GetTranslation(
"UpdateSuccessNoRestart"),
newPlugin.Name));
} }
} }
@ -289,17 +269,17 @@ public static class PluginInstaller
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default) public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
{ {
// Update the plugin manifest // Update the plugin manifest
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); await PublicApi.Instance.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
// Get all plugins that can be updated // Get all plugins that can be updated
var resultsForUpdate = ( var resultsForUpdate = (
from existingPlugin in API.GetAllPlugins() from existingPlugin in PublicApi.Instance.GetAllPlugins()
join pluginUpdateSource in API.GetPluginManifest() join pluginUpdateSource in PublicApi.Instance.GetPluginManifest()
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) < StringComparison.InvariantCulture) <
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) 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 select
new PluginUpdateInfo() new PluginUpdateInfo()
{ {
@ -314,25 +294,25 @@ public static class PluginInstaller
}).ToList(); }).ToList();
// No updates // No updates
if (!resultsForUpdate.Any()) if (resultsForUpdate.Count == 0)
{ {
if (!silentUpdate) if (!silentUpdate)
{ {
API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle")); PublicApi.Instance.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle());
} }
return; return;
} }
// If all plugins are modified, just 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; return;
} }
// Show message box with button to update all plugins // Show message box with button to update all plugins
API.ShowMsgWithButton( PublicApi.Instance.ShowMsgWithButton(
API.GetTranslation("updateAllPluginsTitle"), Localize.updateAllPluginsTitle(),
API.GetTranslation("updateAllPluginsButtonContent"), Localize.updateAllPluginsButtonContent(),
() => () =>
{ {
updateAllPlugins(resultsForUpdate); updateAllPlugins(resultsForUpdate);
@ -357,7 +337,7 @@ public static class PluginInstaller
using var cts = new CancellationTokenSource(); using var cts = new CancellationTokenSource();
await DownloadFileAsync( await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}", $"{Localize.DownloadingPlugin()} {plugin.PluginNewUserPlugin.Name}",
plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts); plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);
// check if user cancelled download before installing plugin // check if user cancelled download before installing plugin
@ -366,7 +346,7 @@ public static class PluginInstaller
return; return;
} }
if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) if (!await PublicApi.Instance.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath))
{ {
return; return;
} }
@ -375,8 +355,8 @@ public static class PluginInstaller
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to update plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
} }
})); }));
@ -384,13 +364,13 @@ public static class PluginInstaller
if (restart) if (restart)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("updatebtn"), Localize.updatebtn(),
API.GetTranslation("PluginsUpdateSuccessNoRestart")); Localize.PluginsUpdateSuccessNoRestart());
} }
} }
@ -412,7 +392,7 @@ public static class PluginInstaller
if (showProgress) if (showProgress)
{ {
var exceptionHappened = false; var exceptionHappened = false;
await API.ShowProgressBoxAsync(progressBoxTitle, await PublicApi.Instance.ShowProgressBoxAsync(progressBoxTitle,
async (reportProgress) => async (reportProgress) =>
{ {
if (reportProgress == null) if (reportProgress == null)
@ -424,18 +404,18 @@ public static class PluginInstaller
} }
else else
{ {
await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false); await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
} }
}, cts.Cancel); }, cts.Cancel);
// if exception happened while downloading and user does not cancel downloading, // if exception happened while downloading and user does not cancel downloading,
// we need to redownload the plugin // we need to redownload the plugin
if (exceptionHappened && (!cts.IsCancellationRequested)) 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 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) if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost)
return false; return false;
return API.GetAllPlugins().Any(x => return PublicApi.Instance.GetAllPlugins().Any(x =>
!string.IsNullOrEmpty(x.Metadata.Website) && !string.IsNullOrEmpty(x.Metadata.Website) &&
x.Metadata.Website.StartsWith(constructedUrlPart) x.Metadata.Website.StartsWith(constructedUrlPart)
); );

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.DialogJump; using Flow.Launcher.Infrastructure.DialogJump;
@ -29,10 +28,6 @@ namespace Flow.Launcher.Core.Plugin
public static readonly HashSet<PluginPair> GlobalPlugins = new(); public static readonly HashSet<PluginPair> GlobalPlugins = new();
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new(); public static readonly Dictionary<string, PluginPair> 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<IPublicAPI>();
private static PluginsSettings Settings; private static PluginsSettings Settings;
private static readonly ConcurrentBag<string> ModifiedPlugins = new(); private static readonly ConcurrentBag<string> ModifiedPlugins = new();
@ -75,12 +70,12 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) 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(); PublicApi.Instance.SavePluginSettings();
API.SavePluginCaches(); PublicApi.Instance.SavePluginCaches();
} }
public static async ValueTask DisposePluginsAsync() public static async ValueTask DisposePluginsAsync()
@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) 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)) 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 continue; // Skip if AssemblyName is not set, which can happen for erroneous plugins
} }
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName);
@ -228,7 +223,7 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (string.IsNullOrEmpty(metadata.Name)) 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 continue; // Skip if Name is not set, which can happen for erroneous plugins
} }
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name);
@ -249,28 +244,28 @@ namespace Flow.Launcher.Core.Plugin
{ {
try try
{ {
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>",
() => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API))); () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, PublicApi.Instance)));
pair.Metadata.InitTime += milliseconds; pair.Metadata.InitTime += milliseconds;
API.LogInfo(ClassName, PublicApi.Instance.LogInfo(ClassName,
$"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
} }
catch (Exception e) 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 (pair.Metadata.Disabled && pair.Metadata.HomeDisabled)
{ {
// If this plugin is already disabled, do not show error message again // If this plugin is already disabled, do not show error message again
// Or else it will be shown every time // 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 else
{ {
pair.Metadata.Disabled = true; pair.Metadata.Disabled = true;
pair.Metadata.HomeDisabled = true; pair.Metadata.HomeDisabled = true;
failedPlugins.Enqueue(pair); 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)); var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("failedToInitializePluginsTitle"), Localize.failedToInitializePluginsTitle(),
string.Format( Localize.failedToInitializePluginsMessage(failed),
API.GetTranslation("failedToInitializePluginsMessage"),
failed
),
"", "",
false false
); );
@ -326,7 +318,7 @@ namespace Flow.Launcher.Core.Plugin
if (dialogJump && plugin.Plugin is not IAsyncDialogJump) if (dialogJump && plugin.Plugin is not IAsyncDialogJump)
return Array.Empty<PluginPair>(); return Array.Empty<PluginPair>();
if (API.PluginModified(plugin.Metadata.ID)) if (PublicApi.Instance.PluginModified(plugin.Metadata.ID))
return Array.Empty<PluginPair>(); return Array.Empty<PluginPair>();
return new List<PluginPair> return new List<PluginPair>
@ -347,7 +339,7 @@ namespace Flow.Launcher.Core.Plugin
try 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)); async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -391,7 +383,7 @@ namespace Flow.Launcher.Core.Plugin
try 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)); async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -408,7 +400,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) 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 null;
} }
return results; return results;
@ -421,7 +413,7 @@ namespace Flow.Launcher.Core.Plugin
try 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)); async () => results = await ((IAsyncDialogJump)pair.Plugin).QueryDialogJumpAsync(query, token).ConfigureAwait(false));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -438,7 +430,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) 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 null;
} }
return results; return results;
@ -505,7 +497,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, PublicApi.Instance.LogException(ClassName,
$"Can't load context menus for plugin <{pluginPair.Metadata.Name}>", $"Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
e); e);
} }
@ -636,8 +628,8 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (PluginModified(existingVersion.ID)) if (PluginModified(existingVersion.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), existingVersion.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return false; return false;
} }
@ -669,8 +661,8 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (checkModified && PluginModified(plugin.ID)) if (checkModified && PluginModified(plugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return false; return false;
} }
@ -689,15 +681,15 @@ namespace Flow.Launcher.Core.Plugin
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name),
string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath)); Localize.fileNotFoundMessage(pluginFolderPath));
return false; return false;
} }
if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name),
API.GetTranslation("pluginExistAlreadyMessage")); Localize.pluginExistAlreadyMessage());
return false; return false;
} }
@ -726,7 +718,7 @@ namespace Flow.Launcher.Core.Plugin
var newPluginPath = Path.Combine(installDirectory, folderName); var newPluginPath = Path.Combine(installDirectory, folderName);
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s)); FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s));
try try
{ {
@ -735,7 +727,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) 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) if (checkModified)
@ -750,8 +742,8 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (checkModified && PluginModified(plugin.ID)) if (checkModified && PluginModified(plugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return false; return false;
} }
@ -770,7 +762,7 @@ namespace Flow.Launcher.Core.Plugin
if (removePluginSettings) if (removePluginSettings)
{ {
// For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances // 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.RemovePluginSettings(plugin.AssemblyName);
removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath); removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath);
@ -784,9 +776,9 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
API.ShowMsgError(API.GetTranslation("failedToRemovePluginSettingsTitle"), PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); Localize.failedToRemovePluginSettingsMessage(plugin.Name));
} }
} }
@ -800,9 +792,9 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
API.ShowMsgError(API.GetTranslation("failedToRemovePluginCacheTitle"), PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginCacheTitle(),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); Localize.failedToRemovePluginCacheMessage(plugin.Name));
} }
Settings.RemovePluginSettings(plugin.ID); Settings.RemovePluginSettings(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);

View file

@ -2,9 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005 #pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin
{ {
private static readonly string ClassName = nameof(PluginsLoader); 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<IPublicAPI>();
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings) public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{ {
var dotnetPlugins = DotNetPlugins(metadatas); var dotnetPlugins = DotNetPlugins(metadatas);
@ -64,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin
foreach (var metadata in metadatas) 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; Assembly assembly = null;
IAsyncPlugin plugin = null; IAsyncPlugin plugin = null;
@ -89,19 +82,19 @@ namespace Flow.Launcher.Core.Plugin
#else #else
catch (Exception e) when (assembly == null) 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) 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) 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) 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 #endif
@ -121,12 +114,12 @@ namespace Flow.Launcher.Core.Plugin
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins); var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
var errorMessage = erroredPlugins.Count > 1 ? var errorMessage = erroredPlugins.Count > 1 ?
API.GetTranslation("pluginsHaveErrored") : Localize.pluginsHaveErrored():
API.GetTranslation("pluginHasErrored"); Localize.pluginHasErrored();
API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
API.GetTranslation("referToLogs")); Localize.referToLogs());
} }
return plugins; return plugins;

View file

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
@ -18,10 +17,6 @@ namespace Flow.Launcher.Core.Resource
{ {
private static readonly string ClassName = nameof(Internationalization); 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<IPublicAPI>();
private const string Folder = "Languages"; private const string Folder = "Languages";
private const string DefaultLanguageCode = "en"; private const string DefaultLanguageCode = "en";
private const string DefaultFile = "en.xaml"; private const string DefaultFile = "en.xaml";
@ -104,7 +99,7 @@ namespace Flow.Launcher.Core.Resource
var directory = Path.Combine(Constant.ProgramDirectory, Folder); var directory = Path.Combine(Constant.ProgramDirectory, Folder);
if (!Directory.Exists(directory)) 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; return;
} }
@ -175,7 +170,7 @@ namespace Flow.Launcher.Core.Resource
FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase)); FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase));
if (language == null) 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; return AvailableLanguages.English;
} }
else else
@ -208,7 +203,7 @@ namespace Flow.Launcher.Core.Resource
} }
catch (Exception e) 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 finally
{ {
@ -254,7 +249,7 @@ namespace Flow.Launcher.Core.Resource
// "Do you want to search with pinyin?" // "Do you want to search with pinyin?"
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?"; 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 false;
return true; return true;
@ -311,7 +306,7 @@ namespace Flow.Launcher.Core.Resource
} }
else 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); var english = Path.Combine(folder, DefaultFile);
if (File.Exists(english)) if (File.Exists(english))
{ {
@ -319,7 +314,7 @@ namespace Flow.Launcher.Core.Resource
} }
else 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; return string.Empty;
} }
} }
@ -354,7 +349,7 @@ namespace Flow.Launcher.Core.Resource
} }
else else
{ {
API.LogError(ClassName, $"No Translation for key {key}"); PublicApi.Instance.LogError(ClassName, $"No Translation for key {key}");
return $"No Translation for key {key}"; return $"No Translation for key {key}";
} }
} }
@ -377,7 +372,7 @@ namespace Flow.Launcher.Core.Resource
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
} }
} }
} }

View file

@ -1,15 +1,9 @@
using System.ComponentModel; using System.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource namespace Flow.Launcher.Core.Resource
{ {
public class LocalizedDescriptionAttribute : DescriptionAttribute 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<IPublicAPI>();
private readonly string _resourceKey; private readonly string _resourceKey;
public LocalizedDescriptionAttribute(string resourceKey) public LocalizedDescriptionAttribute(string resourceKey)
@ -21,7 +15,7 @@ namespace Flow.Launcher.Core.Resource
{ {
get get
{ {
string description = API.GetTranslation(_resourceKey); string description = PublicApi.Instance.GetTranslation(_resourceKey);
return string.IsNullOrWhiteSpace(description) ? return string.IsNullOrWhiteSpace(description) ?
string.Format("[[{0}]]", _resourceKey) : description; string.Format("[[{0}]]", _resourceKey) : description;
} }

View file

@ -444,7 +444,7 @@ namespace Flow.Launcher.Core.Resource
_api.LogError(ClassName, $"Theme <{theme}> path can't be found"); _api.LogError(ClassName, $"Theme <{theme}> path can't be found");
if (theme != Constant.DefaultTheme) 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); ChangeTheme(Constant.DefaultTheme);
} }
return false; return false;
@ -454,7 +454,7 @@ namespace Flow.Launcher.Core.Resource
_api.LogError(ClassName, $"Theme <{theme}> fail to parse"); _api.LogError(ClassName, $"Theme <{theme}> fail to parse");
if (theme != Constant.DefaultTheme) 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); ChangeTheme(Constant.DefaultTheme);
} }
return false; return false;

View file

@ -41,8 +41,8 @@ namespace Flow.Launcher.Core
try try
{ {
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("pleaseWait"), _api.ShowMsg(Localize.pleaseWait(),
_api.GetTranslation("update_flowlauncher_update_check")); Localize.update_flowlauncher_update_check());
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false); using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
@ -58,13 +58,13 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion) if (newReleaseVersion <= currentVersion)
{ {
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest")); _api.ShowMsgBox(Localize.update_flowlauncher_already_on_latest());
return; return;
} }
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"), _api.ShowMsg(Localize.update_flowlauncher_update_found(),
_api.GetTranslation("update_flowlauncher_updating")); Localize.update_flowlauncher_updating());
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false); await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
@ -77,10 +77,7 @@ namespace Flow.Launcher.Core
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)); FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination,
(s) => _api.ShowMsgBox(s))) (s) => _api.ShowMsgBox(s)))
_api.ShowMsgBox(string.Format( _api.ShowMsgBox(Localize.update_flowlauncher_fail_moving_portable_user_profile_data(DataLocation.PortableDataPath, targetDestination));
_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath,
targetDestination));
} }
else else
{ {
@ -91,7 +88,7 @@ namespace Flow.Launcher.Core
_api.LogInfo(ClassName, $"Update success:{newVersionTips}"); _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) MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{ {
UpdateManager.RestartApp(Constant.ApplicationFileName); UpdateManager.RestartApp(Constant.ApplicationFileName);
@ -111,8 +108,8 @@ namespace Flow.Launcher.Core
} }
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsgError(_api.GetTranslation("update_flowlauncher_fail"), _api.ShowMsgError(Localize.update_flowlauncher_fail(),
_api.GetTranslation("update_flowlauncher_check_connection")); Localize.update_flowlauncher_check_connection());
} }
finally finally
{ {
@ -150,9 +147,9 @@ namespace Flow.Launcher.Core
return manager; 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; return tips;
} }

View file

@ -11,6 +11,12 @@
"YamlDotNet": "9.1.0" "YamlDotNet": "9.1.0"
} }
}, },
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"FSharp.Core": { "FSharp.Core": {
"type": "Direct", "type": "Direct",
"requested": "[9.0.303, )", "requested": "[9.0.303, )",
@ -259,6 +265,7 @@
"Ben.Demystifier": "[0.4.1, )", "Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )", "BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )", "CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )", "Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )", "InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )", "MemoryPack": "[1.21.4, )",

View file

@ -58,21 +58,17 @@ namespace Flow.Launcher.Infrastructure.DialogJump
private static readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>(); private static readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>();
// 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<IPublicAPI>();
private static HWND _mainWindowHandle = HWND.Null; private static HWND _mainWindowHandle = HWND.Null;
private static readonly Dictionary<DialogJumpExplorerPair, IDialogJumpExplorerWindow> _dialogJumpExplorers = new(); private static readonly Dictionary<DialogJumpExplorerPair, IDialogJumpExplorerWindow> _dialogJumpExplorers = new();
private static DialogJumpExplorerPair _lastExplorer = null; private static DialogJumpExplorerPair _lastExplorer = null;
private static readonly object _lastExplorerLock = new(); private static readonly Lock _lastExplorerLock = new();
private static readonly Dictionary<DialogJumpDialogPair, IDialogJumpDialogWindow> _dialogJumpDialogs = new(); private static readonly Dictionary<DialogJumpDialogPair, IDialogJumpDialogWindow> _dialogJumpDialogs = new();
private static IDialogJumpDialogWindow _dialogWindow = null; 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 _foregroundChangeHook = HWINEVENTHOOK.Null;
private static HWINEVENTHOOK _locationChangeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _locationChangeHook = HWINEVENTHOOK.Null;
@ -89,8 +85,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump
private static DispatcherTimer _dragMoveTimer = null; private static DispatcherTimer _dragMoveTimer = null;
// A list of all file dialog windows that are auto switched already // A list of all file dialog windows that are auto switched already
private static readonly List<HWND> _autoSwitchedDialogs = new(); private static readonly List<HWND> _autoSwitchedDialogs = [];
private static readonly object _autoSwitchedDialogsLock = new(); private static readonly Lock _autoSwitchedDialogsLock = new();
private static HWINEVENTHOOK _moveSizeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _moveSizeHook = HWINEVENTHOOK.Null;
private static readonly WINEVENTPROC _moveProc = MoveSizeCallBack; private static readonly WINEVENTPROC _moveProc = MoveSizeCallBack;
@ -315,7 +311,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
{ {
foreach (var explorer in _dialogJumpExplorers.Keys) 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 explorer.Metadata.Disabled) continue; // Plugin is disabled
var explorerWindow = explorer.Plugin.CheckExplorerWindow(hWnd); var explorerWindow = explorer.Plugin.CheckExplorerWindow(hWnd);
@ -493,7 +489,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
var dialogWindowChanged = false; var dialogWindowChanged = false;
foreach (var dialog in _dialogJumpDialogs.Keys) 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 dialog.Metadata.Disabled) continue; // Plugin is disabled
IDialogJumpDialogWindow dialogWindow; IDialogJumpDialogWindow dialogWindow;
@ -596,7 +592,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
{ {
foreach (var explorer in _dialogJumpExplorers.Keys) 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 explorer.Metadata.Disabled) continue; // Plugin is disabled
var explorerWindow = explorer.Plugin.CheckExplorerWindow(hwnd); var explorerWindow = explorer.Plugin.CheckExplorerWindow(hwnd);
@ -871,7 +867,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
// Then check all dialog windows // Then check all dialog windows
foreach (var dialog in _dialogJumpDialogs.Keys) 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 dialog.Metadata.Disabled) continue; // Plugin is disabled
var dialogWindow = _dialogJumpDialogs[dialog]; var dialogWindow = _dialogJumpDialogs[dialog];
@ -884,7 +880,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
// Finally search for the dialog window again // Finally search for the dialog window again
foreach (var dialog in _dialogJumpDialogs.Keys) 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 dialog.Metadata.Disabled) continue; // Plugin is disabled
IDialogJumpDialogWindow dialogWindow; IDialogJumpDialogWindow dialogWindow;
@ -1067,11 +1063,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump
_navigationLock.Dispose(); _navigationLock.Dispose();
// Stop drag move timer // Stop drag move timer
if (_dragMoveTimer != null) _dragMoveTimer?.Stop();
{ _dragMoveTimer = null;
_dragMoveTimer.Stop();
_dragMoveTimer = null;
}
} }
#endregion #endregion

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -56,6 +57,7 @@
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BitFaster.Caching" Version="2.5.4" /> <PackageReference Include="BitFaster.Caching" Version="2.5.4" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3"> <PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -81,4 +83,15 @@
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0.3" /> <PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="Languages\en.xaml" />
<AdditionalFiles Include="..\Flow.Launcher\Languages\en.xaml">
<Link>Languages\en.xaml</Link>
</AdditionalFiles>
</ItemGroup>
</Project> </Project>

View file

@ -4,10 +4,8 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Flow.Launcher.Infrastructure.Http namespace Flow.Launcher.Infrastructure.Http
@ -20,10 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http
private static readonly HttpClient client = new(); 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<IPublicAPI>();
static Http() static Http()
{ {
// need to be added so it would work on a win10 machine // need to be added so it would work on a win10 machine
@ -82,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http
} }
catch (UriFormatException e) 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); Log.Exception(ClassName, "Unable to parse Uri", e);
} }
} }

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public class CustomBrowserViewModel : BaseModel 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<IPublicAPI>();
public string Name { get; set; } public string Name { get; set; }
[JsonIgnore] [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 Path { get; set; }
public string PrivateArg { get; set; } public string PrivateArg { get; set; }
public bool EnablePrivate { get; set; } public bool EnablePrivate { get; set; }

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public class CustomExplorerViewModel : BaseModel 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<IPublicAPI>();
public string Name { get; set; } public string Name { get; set; }
[JsonIgnore] [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 Path { get; set; }
public string FileArgument { get; set; } = "\"%d\""; public string FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\""; public string DirectoryArgument { get; set; } = "\"%d\"";

View file

@ -1,8 +1,6 @@
using System; using System;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
@ -55,11 +53,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public string Description { get; set; } public string Description { get; set; }
public string LocalizedDescription => API.GetTranslation(Description); public string LocalizedDescription => PublicApi.Instance.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<IPublicAPI>();
public BaseBuiltinShortcutModel(string key, string description) public BaseBuiltinShortcutModel(string key, string description)
{ {

View file

@ -23,6 +23,12 @@
"resolved": "8.4.0", "resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
}, },
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": { "Fody": {
"type": "Direct", "type": "Direct",
"requested": "[6.9.3, )", "requested": "[6.9.3, )",

View file

@ -47,7 +47,7 @@ namespace Flow.Launcher
if (addedActionKeywords.Any(App.API.ActionKeywordAssigned)) if (addedActionKeywords.Any(App.API.ActionKeywordAssigned))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsHasBeenAssigned")); App.API.ShowMsgBox(Localize.newActionKeywordsHasBeenAssigned());
return; return;
} }
@ -63,7 +63,7 @@ namespace Flow.Launcher
if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords)) if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords))
{ {
// User just changes the sequence of action keywords // User just changes the sequence of action keywords
App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsSameAsOld")); App.API.ShowMsgBox(Localize.newActionKeywordsSameAsOld());
} }
else else
{ {

View file

@ -276,7 +276,7 @@ namespace Flow.Launcher
// but if it fails (permissions, etc) then don't keep retrying // but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget // this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false; _settings.StartFlowLauncherOnSystemStartup = false;
API.ShowMsgError(API.GetTranslation("setAutoStartFailed"), e.Message); API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
} }
} }
} }

View file

@ -41,7 +41,7 @@ namespace Flow.Launcher
if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword)) if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("emptyPluginHotkey")); App.API.ShowMsgBox(Localize.emptyPluginHotkey());
return; return;
} }

View file

@ -40,14 +40,14 @@ namespace Flow.Launcher
{ {
if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(Value)) if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(Value))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("emptyShortcut")); App.API.ShowMsgBox(Localize.emptyShortcut());
return; return;
} }
// Check if key is modified or adding a new one // Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key)) if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("duplicateShortcut")); App.API.ShowMsgBox(Localize.duplicateShortcut());
return; return;
} }

View file

@ -37,14 +37,53 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<Target Name="RemoveUnnecessaryRuntimesAfterBuild" AfterTargets="Build"> <Target Name="RemoveUnnecessaryRuntimesAfterBuild" AfterTargets="Build">
<RemoveDir Directories="$(OutputPath)runtimes\browser-wasm;&#xD;&#xA; $(OutputPath)runtimes\linux-arm;&#xD;&#xA; $(OutputPath)runtimes\linux-arm64;&#xD;&#xA; $(OutputPath)runtimes\linux-armel;&#xD;&#xA; $(OutputPath)runtimes\linux-mips64;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-arm;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-arm64;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-x64;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-s390x;&#xD;&#xA; $(OutputPath)runtimes\linux-ppc64le;&#xD;&#xA; $(OutputPath)runtimes\linux-s390x;&#xD;&#xA; $(OutputPath)runtimes\linux-x64;&#xD;&#xA; $(OutputPath)runtimes\linux-x86;&#xD;&#xA; $(OutputPath)runtimes\maccatalyst-arm64;&#xD;&#xA; $(OutputPath)runtimes\maccatalyst-x64;&#xD;&#xA; $(OutputPath)runtimes\osx;&#xD;&#xA; $(OutputPath)runtimes\osx-arm64;&#xD;&#xA; $(OutputPath)runtimes\osx-x64;&#xD;&#xA; $(OutputPath)runtimes\win-arm;&#xD;&#xA; $(OutputPath)runtimes\win-arm64;" /> <RemoveDir Directories="$(OutputPath)runtimes\browser-wasm;
$(OutputPath)runtimes\linux-arm;
$(OutputPath)runtimes\linux-arm64;
$(OutputPath)runtimes\linux-armel;
$(OutputPath)runtimes\linux-mips64;
$(OutputPath)runtimes\linux-musl-arm;
$(OutputPath)runtimes\linux-musl-arm64;
$(OutputPath)runtimes\linux-musl-x64;
$(OutputPath)runtimes\linux-musl-s390x;
$(OutputPath)runtimes\linux-ppc64le;
$(OutputPath)runtimes\linux-s390x;
$(OutputPath)runtimes\linux-x64;
$(OutputPath)runtimes\linux-x86;
$(OutputPath)runtimes\maccatalyst-arm64;
$(OutputPath)runtimes\maccatalyst-x64;
$(OutputPath)runtimes\osx;
$(OutputPath)runtimes\osx-arm64;
$(OutputPath)runtimes\osx-x64;
$(OutputPath)runtimes\win-arm;
$(OutputPath)runtimes\win-arm64;"/>
</Target> </Target>
<Target Name="RemoveUnnecessaryRuntimesAfterPublish" AfterTargets="Publish"> <Target Name="RemoveUnnecessaryRuntimesAfterPublish" AfterTargets="Publish">
<RemoveDir Directories="$(PublishDir)runtimes\browser-wasm;&#xD;&#xA; $(PublishDir)runtimes\linux-arm;&#xD;&#xA; $(PublishDir)runtimes\linux-arm64;&#xD;&#xA; $(PublishDir)runtimes\linux-armel;&#xD;&#xA; $(PublishDir)runtimes\linux-mips64;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-arm;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-arm64;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-x64;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-s390x;&#xD;&#xA; $(PublishDir)runtimes\linux-ppc64le;&#xD;&#xA; $(PublishDir)runtimes\linux-s390x;&#xD;&#xA; $(PublishDir)runtimes\linux-x64;&#xD;&#xA; $(PublishDir)runtimes\linux-x86;&#xD;&#xA; $(PublishDir)runtimes\maccatalyst-arm64;&#xD;&#xA; $(PublishDir)runtimes\maccatalyst-x64;&#xD;&#xA; $(PublishDir)runtimes\osx;&#xD;&#xA; $(PublishDir)runtimes\osx-arm64;&#xD;&#xA; $(PublishDir)runtimes\osx-x64;&#xD;&#xA; $(PublishDir)runtimes\win-arm;&#xD;&#xA; $(PublishDir)runtimes\win-arm64;" /> <RemoveDir Directories="$(PublishDir)runtimes\browser-wasm;
$(PublishDir)runtimes\linux-arm;
$(PublishDir)runtimes\linux-arm64;
$(PublishDir)runtimes\linux-armel;
$(PublishDir)runtimes\linux-mips64;
$(PublishDir)runtimes\linux-musl-arm;
$(PublishDir)runtimes\linux-musl-arm64;
$(PublishDir)runtimes\linux-musl-x64;
$(PublishDir)runtimes\linux-musl-s390x;
$(PublishDir)runtimes\linux-ppc64le;
$(PublishDir)runtimes\linux-s390x;
$(PublishDir)runtimes\linux-x64;
$(PublishDir)runtimes\linux-x86;
$(PublishDir)runtimes\maccatalyst-arm64;
$(PublishDir)runtimes\maccatalyst-x64;
$(PublishDir)runtimes\osx;
$(PublishDir)runtimes\osx-arm64;
$(PublishDir)runtimes\osx-x64;
$(PublishDir)runtimes\win-arm;
$(PublishDir)runtimes\win-arm64;"/>
</Target> </Target>
<ItemGroup> <ItemGroup>
@ -94,6 +133,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.2" /> <PackageReference Include="ChefKeys" Version="0.1.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3"> <PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -123,6 +163,10 @@
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" /> <ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Resources\open.wav"> <Content Include="Resources\open.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

@ -61,8 +61,8 @@ internal static class HotKeyMapper
string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}", string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}",
e.Message, e.Message,
e.StackTrace)); e.StackTrace));
string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }
@ -87,8 +87,8 @@ internal static class HotKeyMapper
e.Message, e.Message,
e.StackTrace, e.StackTrace,
hotkeyStr)); hotkeyStr));
string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }
@ -112,8 +112,8 @@ internal static class HotKeyMapper
string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}", string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}",
e.Message, e.Message,
e.StackTrace)); e.StackTrace));
string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); string errorMsg = Localize.unregisterHotkeyFailed(hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }

View file

@ -1,4 +1,4 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
@ -234,7 +234,7 @@ namespace Flow.Launcher
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
public string EmptyHotkey => App.API.GetTranslation("none"); public string EmptyHotkey => Localize.none();
public ObservableCollection<string> KeysToDisplay { get; set; } = new(); public ObservableCollection<string> KeysToDisplay { get; set; } = new();

View file

@ -33,7 +33,7 @@ public partial class HotkeyControlDialog : ContentDialog
public EResultType ResultType { get; private set; } = EResultType.Cancel; public EResultType ResultType { get; private set; } = EResultType.Cancel;
public string ResultValue { get; private set; } = string.Empty; 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; private static bool isOpenFlowHotkey;
@ -41,7 +41,7 @@ public partial class HotkeyControlDialog : ContentDialog
{ {
WindowTitle = windowTitle switch WindowTitle = windowTitle switch
{ {
"" or null => App.API.GetTranslation("hotkeyRegTitle"), "" or null => Localize.hotkeyRegTitle(),
_ => windowTitle _ => windowTitle
}; };
DefaultHotkey = defaultHotkey; DefaultHotkey = defaultHotkey;
@ -146,10 +146,7 @@ public partial class HotkeyControlDialog : ContentDialog
Alert.Visibility = Visibility.Visible; Alert.Visibility = Visibility.Visible;
if (registeredHotkeyData.RemoveHotkey is not null) if (registeredHotkeyData.RemoveHotkey is not null)
{ {
tbMsg.Text = string.Format( tbMsg.Text = Localize.hotkeyUnavailableEditable(description);
App.API.GetTranslation("hotkeyUnavailableEditable"),
description
);
SaveBtn.IsEnabled = false; SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Collapsed; SaveBtn.Visibility = Visibility.Collapsed;
OverwriteBtn.IsEnabled = true; OverwriteBtn.IsEnabled = true;
@ -158,10 +155,7 @@ public partial class HotkeyControlDialog : ContentDialog
} }
else else
{ {
tbMsg.Text = string.Format( tbMsg.Text = Localize.hotkeyUnavailableUneditable(description);
App.API.GetTranslation("hotkeyUnavailableUneditable"),
description
);
SaveBtn.IsEnabled = false; SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible; SaveBtn.Visibility = Visibility.Visible;
OverwriteBtn.IsEnabled = false; OverwriteBtn.IsEnabled = false;
@ -175,7 +169,7 @@ public partial class HotkeyControlDialog : ContentDialog
if (!CheckHotkeyAvailability(hotkey.Value, true)) if (!CheckHotkeyAvailability(hotkey.Value, true))
{ {
tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); tbMsg.Text = Localize.hotkeyUnavailable();
Alert.Visibility = Visibility.Visible; Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false; SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible; SaveBtn.Visibility = Visibility.Visible;

View file

@ -209,6 +209,8 @@
<system:String x:Key="plugin_query_version">Version</system:String> <system:String x:Key="plugin_query_version">Version</system:String>
<system:String x:Key="plugin_query_web">Website</system:String> <system:String x:Key="plugin_query_web">Website</system:String>
<system:String x:Key="plugin_uninstall">Uninstall</system:String> <system:String x:Key="plugin_uninstall">Uninstall</system:String>
<system:String x:Key="plugin_default_search_delay_time">Search delay time: default</system:String>
<system:String x:Key="plugin_search_delay_time">Search delay time: {0}ms</system:String>
<system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String> <system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String>
<system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String> <system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String>
<system:String x:Key="failedToRemovePluginCacheTitle">Fail to remove plugin cache</system:String> <system:String x:Key="failedToRemovePluginCacheTitle">Fail to remove plugin cache</system:String>
@ -588,7 +590,7 @@
The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General.
</system:String> </system:String>
<system:String x:Key="errorTitle">Error</system:String> <system:String x:Key="errorTitle">Error</system:String>
<system:String x:Key="folderOpenError">An error occurred while opening the folder. {0}</system:String> <system:String x:Key="folderOpenError">An error occurred while opening the folder.</system:String>
<system:String x:Key="browserOpenError">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</system:String> <system:String x:Key="browserOpenError">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</system:String>
<system:String x:Key="fileNotFoundError">File or directory not found: {0}</system:String> <system:String x:Key="fileNotFoundError">File or directory not found: {0}</system:String>

View file

@ -1,4 +1,4 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Media; using System.Media;
@ -145,8 +145,8 @@ namespace Flow.Launcher
_settings.ReleaseNotesVersion = Constant.Version; _settings.ReleaseNotesVersion = Constant.Version;
// Show release note popup with button // Show release note popup with button
App.API.ShowMsgWithButton( App.API.ShowMsgWithButton(
string.Format(App.API.GetTranslation("appUpdateTitle"), Constant.Version), Localize.appUpdateTitle(Constant.Version),
App.API.GetTranslation("appUpdateButtonContent"), Localize.appUpdateButtonContent(),
() => () =>
{ {
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
@ -753,12 +753,12 @@ namespace Flow.Launcher
private void UpdateNotifyIconText() private void UpdateNotifyIconText()
{ {
var menu = _contextMenu; var menu = _contextMenu;
((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") + ((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen() +
" (" + _settings.Hotkey + ")"; " (" + _settings.Hotkey + ")";
((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode"); ((MenuItem)menu.Items[1]).Header = Localize.GameMode();
((MenuItem)menu.Items[2]).Header = App.API.GetTranslation("PositionReset"); ((MenuItem)menu.Items[2]).Header = Localize.PositionReset();
((MenuItem)menu.Items[3]).Header = App.API.GetTranslation("iconTraySettings"); ((MenuItem)menu.Items[3]).Header = Localize.iconTraySettings();
((MenuItem)menu.Items[4]).Header = App.API.GetTranslation("iconTrayExit"); ((MenuItem)menu.Items[4]).Header = Localize.iconTrayExit();
} }
private void InitializeContextMenu() private void InitializeContextMenu()
@ -768,31 +768,31 @@ namespace Flow.Launcher
var openIcon = new FontIcon { Glyph = "\ue71e" }; var openIcon = new FontIcon { Glyph = "\ue71e" };
var open = new MenuItem var open = new MenuItem
{ {
Header = App.API.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")",
Icon = openIcon Icon = openIcon
}; };
var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" };
var gamemode = new MenuItem var gamemode = new MenuItem
{ {
Header = App.API.GetTranslation("GameMode"), Header = Localize.GameMode(),
Icon = gamemodeIcon Icon = gamemodeIcon
}; };
var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; var positionresetIcon = new FontIcon { Glyph = "\ue73f" };
var positionreset = new MenuItem var positionreset = new MenuItem
{ {
Header = App.API.GetTranslation("PositionReset"), Header = Localize.PositionReset(),
Icon = positionresetIcon Icon = positionresetIcon
}; };
var settingsIcon = new FontIcon { Glyph = "\ue713" }; var settingsIcon = new FontIcon { Glyph = "\ue713" };
var settings = new MenuItem var settings = new MenuItem
{ {
Header = App.API.GetTranslation("iconTraySettings"), Header = Localize.iconTraySettings(),
Icon = settingsIcon Icon = settingsIcon
}; };
var exitIcon = new FontIcon { Glyph = "\ue7e8" }; var exitIcon = new FontIcon { Glyph = "\ue7e8" };
var exit = new MenuItem var exit = new MenuItem
{ {
Header = App.API.GetTranslation("iconTrayExit"), Header = Localize.iconTrayExit(),
Icon = exitIcon Icon = exitIcon
}; };
@ -802,8 +802,8 @@ namespace Flow.Launcher
settings.Click += (o, e) => App.API.OpenSettingDialog(); settings.Click += (o, e) => App.API.OpenSettingDialog();
exit.Click += (o, e) => Close(); exit.Click += (o, e) => Close();
gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip"); gamemode.ToolTip = Localize.GameModeToolTip();
positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip"); positionreset.ToolTip = Localize.PositionResetToolTip();
_contextMenu.Items.Add(open); _contextMenu.Items.Add(open);
_contextMenu.Items.Add(gamemode); _contextMenu.Items.Add(gamemode);

View file

@ -23,7 +23,7 @@ namespace Flow.Launcher
{ {
var checkBox = new CheckBox 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, IsChecked = true,
Margin = new Thickness(0, 5, 0, 5), Margin = new Thickness(0, 5, 0, 5),
Tag = plugin, Tag = plugin,
@ -50,10 +50,7 @@ namespace Flow.Launcher
{ {
if (sender is not CheckBox cb) return; if (sender is not CheckBox cb) return;
if (cb.Tag is not PluginUpdateInfo plugin) 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) private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
@ -66,7 +63,7 @@ namespace Flow.Launcher
{ {
if (Plugins.Count == 0) if (Plugins.Count == 0)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected")); App.API.ShowMsgBox(Localize.updatePluginNoSelected());
return; return;
} }

View file

@ -184,14 +184,14 @@ namespace Flow.Launcher
if (showDefaultNotification) if (showDefaultNotification)
{ {
ShowMsg( ShowMsg(
$"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}", $"{Localize.copy()} {(isFile ? Localize.fileTitle(): Localize.folderTitle())}",
GetTranslation("completedSuccessfully")); Localize.completedSuccessfully());
} }
} }
else else
{ {
LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception); LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy")); ShowMsgError(Localize.failedToCopy());
} }
} }
else else
@ -209,14 +209,14 @@ namespace Flow.Launcher
if (showDefaultNotification) if (showDefaultNotification)
{ {
ShowMsg( ShowMsg(
$"{GetTranslation("copy")} {GetTranslation("textTitle")}", $"{Localize.copy()} {Localize.textTitle()}",
GetTranslation("completedSuccessfully")); Localize.completedSuccessfully());
} }
} }
else else
{ {
LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception); LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy")); ShowMsgError(Localize.failedToCopy());
} }
} }
} }
@ -393,18 +393,18 @@ namespace Flow.Launcher
} }
catch (Win32Exception ex) when (ex.NativeErrorCode == 2) catch (Win32Exception ex) when (ex.NativeErrorCode == 2)
{ {
LogError(ClassName, "File Manager not found"); LogException(ClassName, "File Manager not found", ex);
ShowMsgError( ShowMsgError(
GetTranslation("fileManagerNotFoundTitle"), Localize.fileManagerNotFoundTitle(),
string.Format(GetTranslation("fileManagerNotFound"), ex.Message) Localize.fileManagerNotFound()
); );
} }
catch (Exception ex) catch (Exception ex)
{ {
LogException(ClassName, "Failed to open folder", ex); LogException(ClassName, "Failed to open folder", ex);
ShowMsgError( ShowMsgError(
GetTranslation("errorTitle"), Localize.errorTitle(),
string.Format(GetTranslation("folderOpenError"), ex.Message) Localize.folderOpenError()
); );
} }
} }
@ -413,7 +413,7 @@ namespace Flow.Launcher
{ {
if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath)) if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath))
{ {
ShowMsgError(GetTranslation("errorTitle"), string.Format(GetTranslation("fileNotFoundError"), uri.LocalPath)); ShowMsgError(Localize.errorTitle(), Localize.fileNotFoundError(uri.LocalPath));
return; return;
} }
@ -439,8 +439,8 @@ namespace Flow.Launcher
var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window"; var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window";
LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e); LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e);
ShowMsgError( ShowMsgError(
GetTranslation("errorTitle"), Localize.errorTitle(),
GetTranslation("browserOpenError") Localize.browserOpenError()
); );
} }
} }
@ -457,7 +457,7 @@ namespace Flow.Launcher
catch (Exception e) catch (Exception e)
{ {
LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e); LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e);
ShowMsgError(GetTranslation("errorTitle"), e.Message); ShowMsgError(Localize.errorTitle(), e.Message);
} }
} }
} }

View file

@ -132,8 +132,8 @@ namespace Flow.Launcher
RefreshButton.Visibility = Visibility.Visible; RefreshButton.Visibility = Visibility.Visible;
MarkdownViewer.Visibility = Visibility.Collapsed; MarkdownViewer.Visibility = Visibility.Collapsed;
App.API.ShowMsgError( App.API.ShowMsgError(
App.API.GetTranslation("checkNetworkConnectionTitle"), Localize.checkNetworkConnectionTitle(),
App.API.GetTranslation("checkNetworkConnectionSubTitle")); Localize.checkNetworkConnectionSubTitle());
} }
else else
{ {

View file

@ -48,10 +48,10 @@ namespace Flow.Launcher
_ => Constant.IssuesUrl _ => Constant.IssuesUrl
}; };
var paragraph = Hyperlink(App.API.GetTranslation("reportWindow_please_open_issue"), websiteUrl); var paragraph = Hyperlink(Localize.reportWindow_please_open_issue(), websiteUrl);
paragraph.Inlines.Add(string.Format(App.API.GetTranslation("reportWindow_upload_log"), log.FullName)); paragraph.Inlines.Add(Localize.reportWindow_upload_log(log.FullName));
paragraph.Inlines.Add("\n"); 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); ErrorTextbox.Document.Blocks.Add(paragraph);
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();

View file

@ -59,7 +59,7 @@ namespace Flow.Launcher.Resources.Pages
} }
catch (Exception e) catch (Exception e)
{ {
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
} }
} }

View file

@ -25,7 +25,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
get get
{ {
var size = GetLogFiles().Sum(file => file.Length); 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 get
{ {
var size = GetCacheFiles().Sum(file => file.Length); 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 _ => Constant.Version
}; };
public string ActivatedTimes => string.Format( public string ActivatedTimes => Localize.about_activate_times(_settings.ActivateTimes);
App.API.GetTranslation("about_activate_times"),
_settings.ActivateTimes
);
public class LogLevelData : DropdownDataGeneric<LOGLEVEL> { } public class LogLevelData : DropdownDataGeneric<LOGLEVEL> { }
@ -98,8 +95,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel
private void AskClearLogFolderConfirmation() private void AskClearLogFolderConfirmation()
{ {
var confirmResult = App.API.ShowMsgBox( var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearlogfolderMessage"), Localize.clearlogfolderMessage(),
App.API.GetTranslation("clearlogfolder"), Localize.clearlogfolder(),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -107,7 +104,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{ {
if (!ClearLogFolder()) 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() private void AskClearCacheFolderConfirmation()
{ {
var confirmResult = App.API.ShowMsgBox( var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearcachefolderMessage"), Localize.clearcachefolderMessage(),
App.API.GetTranslation("clearcachefolder"), Localize.clearcachefolder(),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -125,7 +122,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{ {
if (!ClearCacheFolder()) if (!ClearCacheFolder())
{ {
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); App.API.ShowMsgBox(Localize.clearfolderfailMessage());
} }
} }
} }

View file

@ -65,7 +65,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
} }
catch (Exception e) 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) 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 else
{ {
// Since this is rarely seen text, language support is not provided. // 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<Language> Languages => _translater.LoadAvailableLanguages(); public List<Language> Languages => _translater.LoadAvailableLanguages();
public string AlwaysPreviewToolTip => string.Format( public string AlwaysPreviewToolTip => Localize.AlwaysPreviewToolTip(Settings.PreviewHotkey);
App.API.GetTranslation("AlwaysPreviewToolTip"),
Settings.PreviewHotkey
);
private static string GetFileFromDialog(string title, string filter = "") private static string GetFileFromDialog(string title, string filter = "")
{ {
@ -372,7 +369,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void SelectPython() private void SelectPython()
{ {
var selectedFile = GetFileFromDialog( var selectedFile = GetFileFromDialog(
App.API.GetTranslation("selectPythonExecutable"), Localize.selectPythonExecutable(),
"Python|pythonw.exe" "Python|pythonw.exe"
); );
@ -384,7 +381,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void SelectNode() private void SelectNode()
{ {
var selectedFile = GetFileFromDialog( var selectedFile = GetFileFromDialog(
App.API.GetTranslation("selectNodeExecutable"), Localize.selectNodeExecutable(),
"node|*.exe" "node|*.exe"
); );

View file

@ -50,15 +50,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomPluginHotkey; var item = SelectedCustomPluginHotkey;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
var result = App.API.ShowMsgBox( var result = App.API.ShowMsgBox(
string.Format( Localize.deleteCustomHotkeyWarning(item.Hotkey),
App.API.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey Localize.delete(),
),
App.API.GetTranslation("delete"),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -75,7 +73,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomPluginHotkey; var item = SelectedCustomPluginHotkey;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
@ -83,7 +81,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (settingItem == null) if (settingItem == null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("invalidPluginHotkey")); App.API.ShowMsgBox(Localize.invalidPluginHotkey());
return; return;
} }
@ -114,15 +112,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomShortcut; var item = SelectedCustomShortcut;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
var result = App.API.ShowMsgBox( var result = App.API.ShowMsgBox(
string.Format( Localize.deleteCustomShortcutWarning(item.Key, item.Value),
App.API.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value Localize.delete(),
),
App.API.GetTranslation("delete"),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -138,7 +134,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomShortcut; var item = SelectedCustomShortcut;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
@ -146,7 +142,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
o.Key == item.Key && o.Value == item.Value); o.Key == item.Key && o.Value == item.Value);
if (settingItem == null) if (settingItem == null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("invalidShortcut")); App.API.ShowMsgBox(Localize.invalidShortcut());
return; return;
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -103,8 +103,8 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
private async Task InstallPluginAsync() private async Task InstallPluginAsync()
{ {
var file = GetFileFromDialog( var file = GetFileFromDialog(
App.API.GetTranslation("SelectZipFile"), Localize.SelectZipFile(),
$"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip"); $"{Localize.ZipFiles()} (*.zip)|*.zip");
if (!string.IsNullOrEmpty(file)) if (!string.IsNullOrEmpty(file))
await PluginInstaller.InstallPluginAndCheckRestartAsync(file); await PluginInstaller.InstallPluginAndCheckRestartAsync(file);

View file

@ -26,7 +26,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
private readonly Theme _theme; private readonly Theme _theme;
private readonly string DefaultFont = Win32Helper.GetSystemDefaultFont(); 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 LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; 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 public string PlaceholderTextTip
{ {
get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder")); get => Localize.PlaceholderTextTip(Localize.queryTextBoxPlaceholder());
} }
public string PlaceholderText public string PlaceholderText
@ -447,8 +447,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
{ {
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleExplorer"), Title = Localize.SampleTitleExplorer(),
SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"), SubTitle = Localize.SampleSubTitleExplorer(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
@ -456,8 +456,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
}, },
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleWebSearch"), Title = Localize.SampleTitleWebSearch(),
SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"), SubTitle = Localize.SampleSubTitleWebSearch(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
@ -465,8 +465,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
}, },
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleProgram"), Title = Localize.SampleTitleProgram(),
SubTitle = App.API.GetTranslation("SampleSubTitleProgram"), SubTitle = Localize.SampleSubTitleProgram(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
@ -474,8 +474,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
}, },
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleProcessKiller"), Title = Localize.SampleTitleProcessKiller(),
SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"), SubTitle = Localize.SampleSubTitleProcessKiller(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"

View file

@ -342,8 +342,8 @@ namespace Flow.Launcher.ViewModel
Hide(); Hide();
await PluginManager.ReloadDataAsync().ConfigureAwait(false); await PluginManager.ReloadDataAsync().ConfigureAwait(false);
App.API.ShowMsg(App.API.GetTranslation("success"), App.API.ShowMsg(Localize.success(),
App.API.GetTranslation("completedSuccessfully")); Localize.completedSuccessfully());
} }
[RelayCommand] [RelayCommand]
@ -908,7 +908,7 @@ namespace Flow.Launcher.ViewModel
private string _placeholderText; private string _placeholderText;
public string PlaceholderText public string PlaceholderText
{ {
get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText; get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText;
set set
{ {
_placeholderText = value; _placeholderText = value;
@ -1312,12 +1312,10 @@ namespace Flow.Launcher.ViewModel
var results = new List<Result>(); var results = new List<Result>();
foreach (var h in historyItems) foreach (var h in historyItems)
{ {
var title = App.API.GetTranslation("executeQuery");
var time = App.API.GetTranslation("lastExecuteTime");
var result = new Result var result = new Result
{ {
Title = string.Format(title, h.Query), Title = Localize.executeQuery(h.Query),
SubTitle = string.Format(time, h.ExecutedDateTime), SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
IcoPath = Constant.HistoryIcon, IcoPath = Constant.HistoryIcon,
OriginQuery = new Query { RawQuery = h.Query }, OriginQuery = new Query { RawQuery = h.Query },
Action = _ => Action = _ =>
@ -1714,13 +1712,13 @@ namespace Flow.Launcher.ViewModel
{ {
menu = new Result menu = new Result
{ {
Title = App.API.GetTranslation("cancelTopMostInThisQuery"), Title = Localize.cancelTopMostInThisQuery(),
IcoPath = "Images\\down.png", IcoPath = "Images\\down.png",
PluginDirectory = Constant.ProgramDirectory, PluginDirectory = Constant.ProgramDirectory,
Action = _ => Action = _ =>
{ {
_topMostRecord.Remove(result); _topMostRecord.Remove(result);
App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ShowMsg(Localize.success());
App.API.ReQuery(); App.API.ReQuery();
return false; return false;
}, },
@ -1732,13 +1730,13 @@ namespace Flow.Launcher.ViewModel
{ {
menu = new Result menu = new Result
{ {
Title = App.API.GetTranslation("setAsTopMostInThisQuery"), Title = Localize.setAsTopMostInThisQuery(),
IcoPath = "Images\\up.png", IcoPath = "Images\\up.png",
PluginDirectory = Constant.ProgramDirectory, PluginDirectory = Constant.ProgramDirectory,
Action = _ => Action = _ =>
{ {
_topMostRecord.AddOrUpdate(result); _topMostRecord.AddOrUpdate(result);
App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ShowMsg(Localize.success());
App.API.ReQuery(); App.API.ReQuery();
return false; return false;
}, },
@ -1756,10 +1754,10 @@ namespace Flow.Launcher.ViewModel
var metadata = PluginManager.GetPluginForId(id).Metadata; var metadata = PluginManager.GetPluginForId(id).Metadata;
var translator = App.API; var translator = App.API;
var author = translator.GetTranslation("author"); var author = Localize.author();
var website = translator.GetTranslation("website"); var website = Localize.website();
var version = translator.GetTranslation("version"); var version = Localize.version();
var plugin = translator.GetTranslation("plugin"); var plugin = Localize.plugin();
var title = $"{plugin}: {metadata.Name}"; var title = $"{plugin}: {metadata.Name}";
var icon = metadata.IcoPath; var icon = metadata.IcoPath;
var subtitle = $"{author} {metadata.Author}"; var subtitle = $"{author} {metadata.Author}";

View file

@ -155,8 +155,7 @@ namespace Flow.Launcher.ViewModel
App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e); App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e);
// Show error message in UI // Show error message in UI
var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"), var errorMsg = Localize.errorCreatingSettingPanel(pair.Metadata.Name, Environment.NewLine, e.Message);
pair.Metadata.Name, Environment.NewLine, e.Message);
return CreateErrorSettingPanel(errorMsg); return CreateErrorSettingPanel(errorMsg);
} }
} }
@ -165,16 +164,16 @@ namespace Flow.Launcher.ViewModel
Visibility.Collapsed : Visibility.Visible; Visibility.Collapsed : Visibility.Visible;
public string InitializeTime => PluginPair.Metadata.InitTime + "ms"; public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
public string QueryTime => PluginPair.Metadata.AvgQueryTime + "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 => public string InitAndQueryTime =>
App.API.GetTranslation("plugin_init_time") + " " + Localize.plugin_init_time() + " " +
PluginPair.Metadata.InitTime + "ms, " + PluginPair.Metadata.InitTime + "ms, " +
App.API.GetTranslation("plugin_query_time") + " " + Localize.plugin_query_time() + " " +
PluginPair.Metadata.AvgQueryTime + "ms"; PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ? public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ?
App.API.GetTranslation("default") : Localize.plugin_default_search_delay_time() :
App.API.GetTranslation($"SearchDelayTime{PluginPair.Metadata.SearchDelayTime}"); Localize.plugin_search_delay_time(PluginPair.Metadata.SearchDelayTime);
public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; } public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; }
public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay; public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay;
public string DefaultSearchDelay => Settings.SearchDelayTime.ToString(); public string DefaultSearchDelay => Settings.SearchDelayTime.ToString();

View file

@ -50,7 +50,7 @@ public partial class SelectBrowserViewModel : BaseModel
{ {
CustomBrowsers.Add(new() CustomBrowsers.Add(new()
{ {
Name = App.API.GetTranslation("defaultBrowser_new_profile") Name = Localize.defaultBrowser_new_profile()
}); });
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1; SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
} }

View file

@ -48,9 +48,8 @@ public partial class SelectFileManagerViewModel : BaseModel
if (!IsFileManagerValid(CustomExplorer.Path)) if (!IsFileManagerValid(CustomExplorer.Path))
{ {
var result = App.API.ShowMsgBox( var result = App.API.ShowMsgBox(
string.Format(App.API.GetTranslation("fileManagerPathNotFound"), Localize.fileManagerPathNotFound(CustomExplorer.Name, CustomExplorer.Path),
CustomExplorer.Name, CustomExplorer.Path), Localize.fileManagerPathError(),
App.API.GetTranslation("fileManagerPathError"),
MessageBoxButton.YesNo, MessageBoxButton.YesNo,
MessageBoxImage.Warning); MessageBoxImage.Warning);
@ -105,7 +104,7 @@ public partial class SelectFileManagerViewModel : BaseModel
{ {
CustomExplorers.Add(new() CustomExplorers.Add(new()
{ {
Name = App.API.GetTranslation("defaultBrowser_new_profile") Name = Localize.defaultBrowser_new_profile()
}); });
SelectedCustomExplorerIndex = CustomExplorers.Count - 1; SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
} }

View file

@ -14,6 +14,12 @@
"resolved": "8.4.0", "resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
}, },
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": { "Fody": {
"type": "Direct", "type": "Direct",
"requested": "[6.9.3, )", "requested": "[6.9.3, )",
@ -845,6 +851,7 @@
"Droplex": "[1.7.0, )", "Droplex": "[1.7.0, )",
"FSharp.Core": "[9.0.303, )", "FSharp.Core": "[9.0.303, )",
"Flow.Launcher.Infrastructure": "[1.0.0, )", "Flow.Launcher.Infrastructure": "[1.0.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )", "Flow.Launcher.Plugin": "[5.0.0, )",
"Meziantou.Framework.Win32.Jobs": "[3.4.4, )", "Meziantou.Framework.Win32.Jobs": "[3.4.4, )",
"Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )",
@ -859,6 +866,7 @@
"Ben.Demystifier": "[0.4.1, )", "Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )", "BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )", "CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )", "Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )", "InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )", "MemoryPack": "[1.21.4, )",