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.Reflection;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.Win32;
using Squirrel;
@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration
{
private static readonly string ClassName = nameof(Portable);
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary>
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
/// </summary>
@ -45,13 +41,13 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.PortableDataPath);
API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode"));
PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
catch (Exception e)
{
API.LogException(ClassName, "Error occurred while disabling portable mode", e);
PublicApi.Instance.LogException(ClassName, "Error occurred while disabling portable mode", e);
}
}
@ -68,13 +64,13 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.RoamingDataPath);
API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode"));
PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
catch (Exception e)
{
API.LogException(ClassName, "Error occurred while enabling portable mode", e);
PublicApi.Instance.LogException(ClassName, "Error occurred while enabling portable mode", e);
}
}
@ -94,13 +90,13 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation)
{
FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
FilesFolders.CopyAll(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
VerifyUserDataAfterMove(fromLocation, toLocation);
}
public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
{
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
}
public void CreateShortcuts()
@ -150,12 +146,12 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s));
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => PublicApi.Instance.ShowMsgBox(s));
if (API.ShowMsgBox(API.GetTranslation("moveToDifferentLocation"),
if (PublicApi.Instance.ShowMsgBox(Localize.moveToDifferentLocation(),
string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s));
FilesFolders.OpenPath(Constant.RootDirectory, (s) => PublicApi.Instance.ShowMsgBox(s));
Environment.Exit(0);
}
@ -164,9 +160,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s));
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => PublicApi.Instance.ShowMsgBox(s));
API.ShowMsgBox(API.GetTranslation("shortcutsUninstallerCreated"));
PublicApi.Instance.ShowMsgBox(Localize.shortcutsUninstallerCreated());
}
}
@ -177,8 +173,7 @@ namespace Flow.Launcher.Core.Configuration
if (roamingLocationExists && portableLocationExists)
{
API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"),
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
return false;
}

View file

@ -8,7 +8,6 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Plugin;
@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins
{
private static readonly string ClassName = nameof(CommunityPluginSource);
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
private string latestEtag = "";
private List<UserPlugin> plugins = new();
private List<UserPlugin> plugins = [];
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{
@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
/// </remarks>
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);
@ -59,40 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins
.ConfigureAwait(false);
latestEtag = response.Headers.ETag?.Tag;
API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
PublicApi.Instance.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
return plugins;
}
else if (response.StatusCode == HttpStatusCode.NotModified)
{
API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
PublicApi.Instance.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
return plugins;
}
else
{
API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
PublicApi.Instance.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
return null;
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
PublicApi.Instance.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
return null;
}
catch (TaskCanceledException)
{
// Likely an HttpClient timeout or external cancellation not requested by our token
API.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out.");
PublicApi.Instance.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out.");
return null;
}
catch (Exception e)
{
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
{
API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
PublicApi.Instance.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
}
else
{
API.LogException(ClassName, "Error Occurred", e);
PublicApi.Instance.LogException(ClassName, "Error Occurred", e);
}
return null;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure;
@ -23,10 +22,6 @@ namespace Flow.Launcher.Core.ExternalPlugins
private static DateTime lastFetchedAt = DateTime.MinValue;
private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
public static List<UserPlugin> UserPlugins { get; private set; }
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
@ -61,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (Exception e)
{
API.LogException(ClassName, "Http request failed", e);
PublicApi.Instance.LogException(ClassName, "Http request failed", e);
}
finally
{
@ -83,12 +78,12 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (Exception e)
{
API.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. "
PublicApi.Instance.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. "
+ "Plugin excluded from manifest", e);
return false;
}
API.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, "
PublicApi.Instance.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, "
+ $"but current version is {Constant.Version}. Plugin excluded from manifest.");
return false;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -55,6 +56,7 @@
<ItemGroup>
<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="Meziantou.Framework.Win32.Jobs" Version="3.4.4" />
<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="StreamJsonRpc" Version="2.22.11" />
</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>
<ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger;
@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin
{
private static readonly string ClassName = nameof(PluginsLoader);
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas);
@ -64,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin
foreach (var metadata in metadatas)
{
var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () =>
var milliseconds = PublicApi.Instance.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () =>
{
Assembly assembly = null;
IAsyncPlugin plugin = null;
@ -89,19 +82,19 @@ namespace Flow.Launcher.Core.Plugin
#else
catch (Exception e) when (assembly == null)
{
Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
}
catch (ReflectionTypeLoadException e)
{
Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
}
catch (Exception e)
{
Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}
#endif
@ -121,12 +114,12 @@ namespace Flow.Launcher.Core.Plugin
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
var errorMessage = erroredPlugins.Count > 1 ?
API.GetTranslation("pluginsHaveErrored") :
API.GetTranslation("pluginHasErrored");
Localize.pluginsHaveErrored():
Localize.pluginHasErrored();
API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
API.GetTranslation("referToLogs"));
Localize.referToLogs());
}
return plugins;

View file

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

View file

@ -1,15 +1,9 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource
{
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
private readonly string _resourceKey;
public LocalizedDescriptionAttribute(string resourceKey)
@ -21,7 +15,7 @@ namespace Flow.Launcher.Core.Resource
{
get
{
string description = API.GetTranslation(_resourceKey);
string description = PublicApi.Instance.GetTranslation(_resourceKey);
return string.IsNullOrWhiteSpace(description) ?
string.Format("[[{0}]]", _resourceKey) : description;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,10 +4,8 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using JetBrains.Annotations;
namespace Flow.Launcher.Infrastructure.Http
@ -20,10 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http
private static readonly HttpClient client = new();
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
static Http()
{
// need to be added so it would work on a win10 machine
@ -82,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http
}
catch (UriFormatException e)
{
API.ShowMsgError(API.GetTranslation("pleaseTryAgain"), API.GetTranslation("parseProxyFailed"));
PublicApi.Instance.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed());
Log.Exception(ClassName, "Unable to parse Uri", e);
}
}

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class CustomBrowserViewModel : BaseModel
{
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
public string Name { get; set; }
[JsonIgnore]
public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name;
public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default() : Name;
public string Path { get; set; }
public string PrivateArg { get; set; }
public bool EnablePrivate { get; set; }

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class CustomExplorerViewModel : BaseModel
{
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
public string Name { get; set; }
[JsonIgnore]
public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name;
public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer() : Name;
public string Path { get; set; }
public string FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\"";

View file

@ -1,8 +1,6 @@
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
@ -55,11 +53,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public string Description { get; set; }
public string LocalizedDescription => API.GetTranslation(Description);
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
public string LocalizedDescription => PublicApi.Instance.GetTranslation(Description);
public BaseBuiltinShortcutModel(string key, string description)
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -37,14 +37,53 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<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 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>
<ItemGroup>
@ -94,6 +133,7 @@
<ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -123,6 +163,10 @@
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<Content Include="Resources\open.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

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

View file

@ -1,4 +1,4 @@
using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
@ -234,7 +234,7 @@ namespace Flow.Launcher
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
public string EmptyHotkey => App.API.GetTranslation("none");
public string EmptyHotkey => Localize.none();
public ObservableCollection<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 string ResultValue { get; private set; } = string.Empty;
public static string EmptyHotkey => App.API.GetTranslation("none");
public static string EmptyHotkey => Localize.none();
private static bool isOpenFlowHotkey;
@ -41,7 +41,7 @@ public partial class HotkeyControlDialog : ContentDialog
{
WindowTitle = windowTitle switch
{
"" or null => App.API.GetTranslation("hotkeyRegTitle"),
"" or null => Localize.hotkeyRegTitle(),
_ => windowTitle
};
DefaultHotkey = defaultHotkey;
@ -146,10 +146,7 @@ public partial class HotkeyControlDialog : ContentDialog
Alert.Visibility = Visibility.Visible;
if (registeredHotkeyData.RemoveHotkey is not null)
{
tbMsg.Text = string.Format(
App.API.GetTranslation("hotkeyUnavailableEditable"),
description
);
tbMsg.Text = Localize.hotkeyUnavailableEditable(description);
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Collapsed;
OverwriteBtn.IsEnabled = true;
@ -158,10 +155,7 @@ public partial class HotkeyControlDialog : ContentDialog
}
else
{
tbMsg.Text = string.Format(
App.API.GetTranslation("hotkeyUnavailableUneditable"),
description
);
tbMsg.Text = Localize.hotkeyUnavailableUneditable(description);
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible;
OverwriteBtn.IsEnabled = false;
@ -175,7 +169,7 @@ public partial class HotkeyControlDialog : ContentDialog
if (!CheckHotkeyAvailability(hotkey.Value, true))
{
tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable");
tbMsg.Text = Localize.hotkeyUnavailable();
Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible;

View file

@ -209,6 +209,8 @@
<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_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="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>
@ -588,7 +590,7 @@
The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General.
</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="fileNotFoundError">File or directory not found: {0}</system:String>

View file

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

View file

@ -23,7 +23,7 @@ namespace Flow.Launcher
{
var checkBox = new CheckBox
{
Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
Content = Localize.updatePluginCheckboxContent(plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
IsChecked = true,
Margin = new Thickness(0, 5, 0, 5),
Tag = plugin,
@ -50,10 +50,7 @@ namespace Flow.Launcher
{
if (sender is not CheckBox cb) return;
if (cb.Tag is not PluginUpdateInfo plugin) return;
if (Plugins.Contains(plugin))
{
Plugins.Remove(plugin);
}
Plugins.Remove(plugin);
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
@ -66,7 +63,7 @@ namespace Flow.Launcher
{
if (Plugins.Count == 0)
{
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected"));
App.API.ShowMsgBox(Localize.updatePluginNoSelected());
return;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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