mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-04 21:49:40 +00:00
Configure language & start language plugins (#400)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, deb) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, deb) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
This commit is contained in:
1 parent
171ed37c27
commit
3fc15d9789
31 files changed
+612
-156
No files matched your search
@@ -0,0 +1,6 @@
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public interface IAvailablePlugin : IPluginMetadata
|
||||
{
|
||||
public string LocalPath { get; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a contract to access text from a language plugin.
|
||||
/// </summary>
|
||||
public interface ILang
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to get a text from the language plugin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The given fallback text is used to determine the key for
|
||||
/// the language plugin. Base for the key is the namespace of
|
||||
/// the using component and the fallback text in English (US).
|
||||
/// The given text getting hashed. When the key does not exist,
|
||||
/// the fallback text will be returned.
|
||||
/// </remarks>
|
||||
/// <param name="fallbackEN">The fallback text in English (US).</param>
|
||||
/// <returns>The text from the language plugin or the fallback text.</returns>
|
||||
public string T(string fallbackEN);
|
||||
}
|
||||
@@ -16,6 +16,17 @@ public interface ILanguagePlugin
|
||||
/// </remarks>
|
||||
/// <param name="key">The key to use to get the text.</param>
|
||||
/// <param name="value">The desired text.</param>
|
||||
/// <param name="logWarning">When true, a warning will be logged if the key does not exist.</param>
|
||||
/// <returns>True if the key exists, false otherwise.</returns>
|
||||
public bool TryGetText(string key, out string value);
|
||||
public bool TryGetText(string key, out string value, bool logWarning = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IETF tag of the language plugin.
|
||||
/// </summary>
|
||||
public string IETFTag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the language.
|
||||
/// </summary>
|
||||
public string LangName { get; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Lua;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public sealed class NoPluginLanguage : PluginBase, ILanguagePlugin
|
||||
{
|
||||
public static readonly NoPluginLanguage INSTANCE = new();
|
||||
|
||||
private NoPluginLanguage() : base(true, LuaState.Create(), PluginType.LANGUAGE, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
#region Implementation of ILanguagePlugin
|
||||
|
||||
public bool TryGetText(string key, out string value, bool logWarning = false)
|
||||
{
|
||||
value = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public string IETFTag => string.Empty;
|
||||
|
||||
public string LangName => string.Empty;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -4,6 +4,12 @@ public static partial class PluginFactory
|
||||
{
|
||||
public static void SetUpHotReloading()
|
||||
{
|
||||
if (!IS_INITIALIZED)
|
||||
{
|
||||
LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it.");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.LogInformation($"Start hot reloading plugins for path '{HOT_RELOAD_WATCHER.Path}'.");
|
||||
try
|
||||
{
|
||||
|
||||
@@ -10,6 +10,12 @@ public static partial class PluginFactory
|
||||
{
|
||||
public static async Task EnsureInternalPlugins()
|
||||
{
|
||||
if (!IS_INITIALIZED)
|
||||
{
|
||||
LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it.");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.LogInformation("Start ensuring internal plugins.");
|
||||
foreach (var plugin in Enum.GetValues<InternalPlugin>())
|
||||
{
|
||||
@@ -40,15 +46,15 @@ public static partial class PluginFactory
|
||||
}
|
||||
|
||||
// Ensure that the additional resources exist:
|
||||
foreach (var content in resourceFileProvider.GetDirectoryContents(metaData.ResourcePath))
|
||||
foreach (var contentFilePath in resourceFileProvider.GetDirectoryContents(metaData.ResourcePath))
|
||||
{
|
||||
if(content.IsDirectory)
|
||||
if(contentFilePath.IsDirectory)
|
||||
{
|
||||
LOG.LogError("The plugin contains a directory. This is not allowed.");
|
||||
continue;
|
||||
}
|
||||
|
||||
await CopyInternalPluginFile(content, metaData);
|
||||
await CopyInternalPluginFile(contentFilePath, metaData);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -57,9 +63,9 @@ public static partial class PluginFactory
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CopyInternalPluginFile(IFileInfo resourceInfo, InternalPluginData metaData)
|
||||
private static async Task CopyInternalPluginFile(IFileInfo resourceFilePath, InternalPluginData metaData)
|
||||
{
|
||||
await using var inputStream = resourceInfo.CreateReadStream();
|
||||
await using var inputStream = resourceFilePath.CreateReadStream();
|
||||
|
||||
var pluginTypeBasePath = Path.Join(INTERNAL_PLUGINS_ROOT, metaData.Type.GetDirectory());
|
||||
|
||||
@@ -73,7 +79,7 @@ public static partial class PluginFactory
|
||||
if (!Directory.Exists(pluginPath))
|
||||
Directory.CreateDirectory(pluginPath);
|
||||
|
||||
var pluginFilePath = Path.Join(pluginPath, resourceInfo.Name);
|
||||
var pluginFilePath = Path.Join(pluginPath, resourceFilePath.Name);
|
||||
|
||||
await using var outputStream = File.Create(pluginFilePath);
|
||||
await inputStream.CopyToAsync(outputStream);
|
||||
|
||||
@@ -9,29 +9,45 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public static partial class PluginFactory
|
||||
{
|
||||
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PluginFactory");
|
||||
|
||||
private static readonly string DATA_DIR = SettingsManager.DataDirectory!;
|
||||
|
||||
private static readonly string PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins");
|
||||
|
||||
private static readonly string INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal");
|
||||
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginFactory));
|
||||
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||
private static readonly List<IAvailablePlugin> AVAILABLE_PLUGINS = [];
|
||||
private static readonly List<PluginBase> RUNNING_PLUGINS = [];
|
||||
|
||||
private static readonly FileSystemWatcher HOT_RELOAD_WATCHER;
|
||||
|
||||
private static readonly List<IPluginMetadata> AVAILABLE_PLUGINS = [];
|
||||
private static bool IS_INITIALIZED;
|
||||
private static string DATA_DIR = string.Empty;
|
||||
private static string PLUGINS_ROOT = string.Empty;
|
||||
private static string INTERNAL_PLUGINS_ROOT = string.Empty;
|
||||
private static FileSystemWatcher HOT_RELOAD_WATCHER = null!;
|
||||
private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE;
|
||||
|
||||
/// <summary>
|
||||
/// A list of all available plugins.
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<IPluginMetadata> AvailablePlugins => AVAILABLE_PLUGINS;
|
||||
|
||||
/// <summary>
|
||||
/// A list of all running plugins.
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<PluginBase> RunningPlugins => RUNNING_PLUGINS;
|
||||
|
||||
static PluginFactory()
|
||||
public static ILanguagePlugin BaseLanguage => BASE_LANGUAGE_PLUGIN;
|
||||
|
||||
/// <summary>
|
||||
/// Set up the plugin factory. We will read the data directory from the settings manager.
|
||||
/// Afterward, we will create the plugins directory and the internal plugin directory.
|
||||
/// </summary>
|
||||
public static void Setup()
|
||||
{
|
||||
DATA_DIR = SettingsManager.DataDirectory!;
|
||||
PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins");
|
||||
INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal");
|
||||
|
||||
if (!Directory.Exists(PLUGINS_ROOT))
|
||||
Directory.CreateDirectory(PLUGINS_ROOT);
|
||||
|
||||
HOT_RELOAD_WATCHER = new(PLUGINS_ROOT);
|
||||
IS_INITIALIZED = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,6 +64,12 @@ public static partial class PluginFactory
|
||||
/// </remarks>
|
||||
public static async Task LoadAll(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!IS_INITIALIZED)
|
||||
{
|
||||
LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it.");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.LogInformation("Start loading plugins.");
|
||||
if (!Directory.Exists(PLUGINS_ROOT))
|
||||
{
|
||||
@@ -96,8 +118,11 @@ public static partial class PluginFactory
|
||||
}
|
||||
|
||||
LOG.LogInformation($"Successfully loaded plugin: '{pluginMainFile}' (Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}', Authors='{string.Join(", ", plugin.Authors)}')");
|
||||
AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin));
|
||||
AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin, pluginPath));
|
||||
}
|
||||
|
||||
// Start or restart all plugins:
|
||||
await RestartAllPlugins(cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<PluginBase> Load(string pluginPath, string code, CancellationToken cancellationToken = default)
|
||||
@@ -148,9 +173,100 @@ public static partial class PluginFactory
|
||||
_ => new NoPlugin("This plugin type is not supported yet. Please try again with a future version of AI Studio.")
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task RestartAllPlugins(CancellationToken cancellationToken = default)
|
||||
{
|
||||
LOG.LogInformation("Try to start or restart all plugins.");
|
||||
RUNNING_PLUGINS.Clear();
|
||||
|
||||
//
|
||||
// Get the base language plugin. This is the plugin that will be used to fill in missing keys.
|
||||
//
|
||||
var baseLanguagePluginId = InternalPlugin.LANGUAGE_EN_US.MetaData().Id;
|
||||
var baseLanguagePluginMetaData = AVAILABLE_PLUGINS.FirstOrDefault(p => p.Id == baseLanguagePluginId);
|
||||
if (baseLanguagePluginMetaData is null)
|
||||
{
|
||||
LOG.LogError($"Was not able to find the base language plugin: Id='{baseLanguagePluginId}'. Please check your installation.");
|
||||
return;
|
||||
}
|
||||
|
||||
var startedBasePlugin = await Start(baseLanguagePluginMetaData, cancellationToken);
|
||||
if (startedBasePlugin is NoPlugin noPlugin)
|
||||
{
|
||||
LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {noPlugin.Issues.First()}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startedBasePlugin is PluginLanguage languagePlugin)
|
||||
{
|
||||
BASE_LANGUAGE_PLUGIN = languagePlugin;
|
||||
RUNNING_PLUGINS.Add(languagePlugin);
|
||||
LOG.LogInformation($"Successfully started the base language plugin: Id='{languagePlugin.Id}', Type='{languagePlugin.Type}', Name='{languagePlugin.Name}', Version='{languagePlugin.Version}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {string.Join("; ", startedBasePlugin.Issues)}");
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Iterate over all available plugins and try to start them.
|
||||
//
|
||||
foreach (var availablePlugin in AVAILABLE_PLUGINS)
|
||||
{
|
||||
if(cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (availablePlugin.Id == baseLanguagePluginId)
|
||||
continue;
|
||||
|
||||
if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin))
|
||||
if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin)
|
||||
RUNNING_PLUGINS.Add(plugin);
|
||||
|
||||
// Inform all components that the plugins have been reloaded or started:
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.PLUGINS_RELOADED);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<PluginBase> Start(IAvailablePlugin meta, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var pluginMainFile = Path.Join(meta.LocalPath, "plugin.lua");
|
||||
if(!File.Exists(pluginMainFile))
|
||||
{
|
||||
LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: The plugin file does not exist.");
|
||||
return new NoPlugin($"The plugin file does not exist: {pluginMainFile}");
|
||||
}
|
||||
|
||||
var code = await File.ReadAllTextAsync(pluginMainFile, Encoding.UTF8, cancellationToken);
|
||||
var plugin = await Load(meta.LocalPath, code, cancellationToken);
|
||||
if (plugin is NoPlugin noPlugin)
|
||||
{
|
||||
LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: {noPlugin.Issues.First()}");
|
||||
return noPlugin;
|
||||
}
|
||||
|
||||
if (plugin.IsValid)
|
||||
{
|
||||
//
|
||||
// When this is a language plugin, we need to set the base language plugin.
|
||||
//
|
||||
if (plugin is PluginLanguage languagePlugin && BASE_LANGUAGE_PLUGIN != NoPluginLanguage.INSTANCE)
|
||||
languagePlugin.SetBaseLanguage(BASE_LANGUAGE_PLUGIN);
|
||||
|
||||
LOG.LogInformation($"Successfully started plugin: Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}'");
|
||||
return plugin;
|
||||
}
|
||||
|
||||
LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}");
|
||||
return new NoPlugin($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}");
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
if(!IS_INITIALIZED)
|
||||
return;
|
||||
|
||||
HOT_RELOAD_WATCHER.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,12 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
{
|
||||
private static readonly ILogger<PluginLanguage> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginLanguage>();
|
||||
|
||||
private readonly Dictionary<string, string> content = [];
|
||||
private readonly List<ILanguagePlugin> otherLanguagePlugins = [];
|
||||
private readonly string langCultureTag;
|
||||
private readonly string langName;
|
||||
|
||||
private ILanguagePlugin? baseLanguage;
|
||||
|
||||
@@ -15,6 +18,9 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
if(!this.TryInitIETFTag(out var issue, out this.langCultureTag))
|
||||
this.pluginIssues.Add(issue);
|
||||
|
||||
if(!this.TryInitLangName(out issue, out this.langName))
|
||||
this.pluginIssues.Add(issue);
|
||||
|
||||
if (this.TryInitUITextContent(out issue, out var readContent))
|
||||
this.content = readContent;
|
||||
else
|
||||
@@ -36,39 +42,6 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
/// </remarks>
|
||||
/// <param name="languagePlugin">The language plugin to add.</param>
|
||||
public void AddOtherLanguagePlugin(ILanguagePlugin languagePlugin) => this.otherLanguagePlugins.Add(languagePlugin);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a text from the language plugin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When the key neither in the base language nor in this language exist,
|
||||
/// the value will be an empty string. Please note that the key is case-sensitive.
|
||||
/// Furthermore, the keys are in the format "root::key". That means that
|
||||
/// the keys are hierarchical and separated by "::".
|
||||
/// </remarks>
|
||||
/// <param name="key">The key to use to get the text.</param>
|
||||
/// <param name="value">The desired text.</param>
|
||||
/// <returns>True if the key exists, false otherwise.</returns>
|
||||
public bool TryGetText(string key, out string value)
|
||||
{
|
||||
// First, we check if the key is part of the main language pack:
|
||||
if (this.content.TryGetValue(key, out value!))
|
||||
return true;
|
||||
|
||||
// Second, we check if the key is part of the other language packs, such as the assistant plugins:
|
||||
foreach (var otherLanguagePlugin in this.otherLanguagePlugins)
|
||||
if(otherLanguagePlugin.TryGetText(key, out value))
|
||||
return true;
|
||||
|
||||
// Finally, we check if the key is part of the base language pack. This is the case,
|
||||
// when a language plugin does not cover all keys. In this case, the base language plugin
|
||||
// will be used to fill in the missing keys:
|
||||
if(this.baseLanguage is not null && this.baseLanguage.TryGetText(key, out value))
|
||||
return true;
|
||||
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to initialize the IETF tag.
|
||||
@@ -127,4 +100,71 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryInitLangName(out string message, out string readLangName)
|
||||
{
|
||||
if (!this.state.Environment["LANG_NAME"].TryRead(out readLangName))
|
||||
{
|
||||
message = "The field LANG_NAME does not exist or is not a valid string.";
|
||||
readLangName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(readLangName))
|
||||
{
|
||||
message = "The field LANG_NAME is empty. Use a valid language name.";
|
||||
readLangName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Implementation of ILanguagePlugin
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a text from the language plugin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When the key neither in the base language nor in this language exist,
|
||||
/// the value will be an empty string. Please note that the key is case-sensitive.
|
||||
/// Furthermore, the keys are in the format "root::key". That means that
|
||||
/// the keys are hierarchical and separated by "::".
|
||||
/// </remarks>
|
||||
/// <param name="key">The key to use to get the text.</param>
|
||||
/// <param name="value">The desired text.</param>
|
||||
/// <param name="logWarning">When true, a warning will be logged if the key does not exist.</param>
|
||||
/// <returns>True if the key exists, false otherwise.</returns>
|
||||
public bool TryGetText(string key, out string value, bool logWarning = false)
|
||||
{
|
||||
// First, we check if the key is part of the main language pack:
|
||||
if (this.content.TryGetValue(key, out value!))
|
||||
return true;
|
||||
|
||||
// Second, we check if the key is part of the other language packs, such as the assistant plugins:
|
||||
foreach (var otherLanguagePlugin in this.otherLanguagePlugins)
|
||||
if(otherLanguagePlugin.TryGetText(key, out value))
|
||||
return true;
|
||||
|
||||
// Finally, we check if the key is part of the base language pack. This is the case,
|
||||
// when a language plugin does not cover all keys. In this case, the base language plugin
|
||||
// will be used to fill in the missing keys:
|
||||
if(this.baseLanguage is not null && this.baseLanguage.TryGetText(key, out value))
|
||||
return true;
|
||||
|
||||
if(logWarning)
|
||||
LOGGER.LogWarning($"Missing translation key '{key}'.");
|
||||
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string IETFTag => this.langCultureTag;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string LangName => this.langName;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public sealed class PluginMetadata(PluginBase plugin) : IPluginMetadata
|
||||
public sealed class PluginMetadata(PluginBase plugin, string localPath) : IAvailablePlugin
|
||||
{
|
||||
#region Implementation of IPluginMetadata
|
||||
|
||||
@@ -47,4 +47,10 @@ public sealed class PluginMetadata(PluginBase plugin) : IPluginMetadata
|
||||
public bool IsInternal { get; } = plugin.IsInternal;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IAvailablePlugin
|
||||
|
||||
public string LocalPath { get; } = localPath;
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in new issue
Block a user