2025-04-26 16:55:23 +00:00
using System.Diagnostics ;
using System.Text ;
using AIStudio.Dialogs.Settings ;
2026-07-03 13:02:20 +00:00
using AIStudio.Tools.AssistantSessions ;
2025-04-26 16:55:23 +00:00
using AIStudio.Tools.PluginSystem ;
using Microsoft.Extensions.FileProviders ;
using SharedTools ;
2025-04-26 17:00:10 +00:00
#if RELEASE
using System.Reflection ;
#endif
2025-04-26 16:55:23 +00:00
namespace AIStudio.Assistants.I18N ;
public partial class AssistantI18N : AssistantBaseCore < SettingsDialogI18N >
{
2026-02-01 13:50:19 +00:00
protected override Tools . Components Component = > Tools . Components . I18N_ASSISTANT ;
2025-04-26 16:55:23 +00:00
2025-05-28 21:14:05 +00:00
protected override string Title = > T ( "Localization" ) ;
2025-04-26 16:55:23 +00:00
2025-05-28 21:14:05 +00:00
protected override string Description = > T ( "Translate MindWork AI Studio text content into another language." ) ;
2025-04-26 16:55:23 +00:00
protected override string SystemPrompt = >
$"" "
# Assignment
You are an expert in professional translations from English ( US ) to { this . SystemPromptLanguage ( ) } .
You translate the texts without adding any new information . When necessary , you correct
spelling and grammar .
# Context
The texts to be translated come from the open source app "MindWork AI Studio" . The goal
is to localize the app so that it can be offered in other languages . You will always
receive one text at a time . A text may be , for example , for a button , a label , or an
explanation within the app . The app "AI Studio" is a desktop app for macOS , Linux ,
and Windows . Users can use Large Language Models ( LLMs ) in practical ways in their
daily lives with it . The app offers the regular chat mode for which LLMs have become
known . However , AI Studio also offers so - called assistants , where users no longer
have to prompt .
# Target Audience
The app is intended for everyone , not just IT specialists or scientists . When translating ,
make sure the texts are easy for everyone to understand .
"" ";
protected override bool AllowProfiles = > false ;
protected override bool ShowResult = > false ;
protected override bool ShowCopyResult = > false ;
protected override bool ShowSendTo = > false ;
protected override IReadOnlyList < IButtonData > FooterButtons = >
[
new ButtonData
{
2025-12-16 17:37:57 +00:00
#if DEBUG
Text = T ( "Write Lua code to language plugin file" ) ,
#else
2025-05-28 21:14:05 +00:00
Text = T ( "Copy Lua code to clipboard" ) ,
2025-12-16 17:37:57 +00:00
#endif
2025-04-26 16:55:23 +00:00
Icon = Icons . Material . Filled . Extension ,
Color = Color . Default ,
2025-12-16 17:37:57 +00:00
#if DEBUG
AsyncAction = async ( ) = > await this . WriteToPluginFile ( ) ,
#else
2025-04-26 16:55:23 +00:00
AsyncAction = async ( ) = > await this . RustService . CopyText2Clipboard ( this . Snackbar , this . finalLuaCode . ToString ( ) ) ,
2025-12-16 17:37:57 +00:00
#endif
2025-04-26 16:55:23 +00:00
DisabledActionParam = ( ) = > this . finalLuaCode . Length = = 0 ,
} ,
] ;
2025-05-28 21:14:05 +00:00
protected override string SubmitText = > T ( "Localize AI Studio & generate the Lua code" ) ;
2025-04-26 16:55:23 +00:00
protected override Func < Task > SubmitAction = > this . LocalizeTextContent ;
protected override bool SubmitDisabled = > ! this . localizationPossible ;
protected override bool ShowDedicatedProgress = > true ;
protected override void ResetForm ( )
{
if ( ! this . MightPreselectValues ( ) )
{
this . selectedLanguagePluginId = InternalPlugin . LANGUAGE_EN_US . MetaData ( ) . Id ;
this . selectedTargetLanguage = CommonLanguages . AS_IS ;
this . customTargetLanguage = string . Empty ;
}
_ = this . OnChangedLanguage ( ) ;
}
protected override bool MightPreselectValues ( )
{
if ( this . SettingsManager . ConfigurationData . I18N . PreselectOptions )
{
this . selectedLanguagePluginId = this . SettingsManager . ConfigurationData . I18N . PreselectedLanguagePluginId ;
this . selectedTargetLanguage = this . SettingsManager . ConfigurationData . I18N . PreselectedTargetLanguage ;
this . customTargetLanguage = this . SettingsManager . ConfigurationData . I18N . PreselectOtherLanguage ;
return true ;
}
return false ;
}
private CommonLanguages selectedTargetLanguage ;
private string customTargetLanguage = string . Empty ;
private bool isLoading = true ;
private string loadingIssue = string . Empty ;
private bool localizationPossible ;
private string searchString = string . Empty ;
private Guid selectedLanguagePluginId ;
private ILanguagePlugin ? selectedLanguagePlugin ;
private Dictionary < string , string > addedContent = [ ] ;
private Dictionary < string , string > removedContent = [ ] ;
private Dictionary < string , string > localizedContent = [ ] ;
private StringBuilder finalLuaCode = new ( ) ;
2026-07-03 13:02:20 +00:00
private string? activeSystemPromptLanguage ;
private static readonly AssistantSessionStateKey < CommonLanguages > SELECTED_TARGET_LANGUAGE_STATE_KEY = new ( nameof ( selectedTargetLanguage ) ) ;
private static readonly AssistantSessionStateKey < string > CUSTOM_TARGET_LANGUAGE_STATE_KEY = new ( nameof ( customTargetLanguage ) ) ;
private static readonly AssistantSessionStateKey < bool > IS_LOADING_STATE_KEY = new ( nameof ( isLoading ) ) ;
private static readonly AssistantSessionStateKey < string > LOADING_ISSUE_STATE_KEY = new ( nameof ( loadingIssue ) ) ;
private static readonly AssistantSessionStateKey < bool > LOCALIZATION_POSSIBLE_STATE_KEY = new ( nameof ( localizationPossible ) ) ;
private static readonly AssistantSessionStateKey < string > SEARCH_STRING_STATE_KEY = new ( nameof ( searchString ) ) ;
private static readonly AssistantSessionStateKey < Guid > SELECTED_LANGUAGE_PLUGIN_ID_STATE_KEY = new ( nameof ( selectedLanguagePluginId ) ) ;
private static readonly AssistantSessionStateKey < ILanguagePlugin ? > SELECTED_LANGUAGE_PLUGIN_STATE_KEY = new ( nameof ( selectedLanguagePlugin ) ) ;
private static readonly AssistantSessionStateKey < Dictionary < string , string > > ADDED_CONTENT_STATE_KEY = new ( nameof ( addedContent ) ) ;
private static readonly AssistantSessionStateKey < Dictionary < string , string > > REMOVED_CONTENT_STATE_KEY = new ( nameof ( removedContent ) ) ;
private static readonly AssistantSessionStateKey < Dictionary < string , string > > LOCALIZED_CONTENT_STATE_KEY = new ( nameof ( localizedContent ) ) ;
private static readonly AssistantSessionStateKey < string > FINAL_LUA_CODE_STATE_KEY = new ( nameof ( finalLuaCode ) ) ;
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState ( AssistantSessionStateWriter state )
{
state . Set ( SELECTED_TARGET_LANGUAGE_STATE_KEY , this . selectedTargetLanguage ) ;
state . Set ( CUSTOM_TARGET_LANGUAGE_STATE_KEY , this . customTargetLanguage ) ;
state . Set ( IS_LOADING_STATE_KEY , this . isLoading ) ;
state . Set ( LOADING_ISSUE_STATE_KEY , this . loadingIssue ) ;
state . Set ( LOCALIZATION_POSSIBLE_STATE_KEY , this . localizationPossible ) ;
state . Set ( SEARCH_STRING_STATE_KEY , this . searchString ) ;
state . Set ( SELECTED_LANGUAGE_PLUGIN_ID_STATE_KEY , this . selectedLanguagePluginId ) ;
state . Set ( SELECTED_LANGUAGE_PLUGIN_STATE_KEY , this . selectedLanguagePlugin ) ;
state . SetDictionary ( ADDED_CONTENT_STATE_KEY , this . addedContent ) ;
state . SetDictionary ( REMOVED_CONTENT_STATE_KEY , this . removedContent ) ;
state . SetDictionary ( LOCALIZED_CONTENT_STATE_KEY , this . localizedContent ) ;
state . SetStringBuilder ( FINAL_LUA_CODE_STATE_KEY , this . finalLuaCode ) ;
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState ( AssistantSessionStateReader state )
{
state . Restore ( SELECTED_TARGET_LANGUAGE_STATE_KEY , value = > this . selectedTargetLanguage = value ) ;
state . Restore ( CUSTOM_TARGET_LANGUAGE_STATE_KEY , value = > this . customTargetLanguage = value ) ;
state . Restore ( IS_LOADING_STATE_KEY , value = > this . isLoading = value ) ;
state . Restore ( LOADING_ISSUE_STATE_KEY , value = > this . loadingIssue = value ) ;
state . Restore ( LOCALIZATION_POSSIBLE_STATE_KEY , value = > this . localizationPossible = value ) ;
state . Restore ( SEARCH_STRING_STATE_KEY , value = > this . searchString = value ) ;
state . Restore ( SELECTED_LANGUAGE_PLUGIN_ID_STATE_KEY , value = > this . selectedLanguagePluginId = value ) ;
state . Restore ( SELECTED_LANGUAGE_PLUGIN_STATE_KEY , value = > this . selectedLanguagePlugin = value ) ;
state . RestoreDictionary ( ADDED_CONTENT_STATE_KEY , this . addedContent ) ;
state . RestoreDictionary ( REMOVED_CONTENT_STATE_KEY , this . removedContent ) ;
state . RestoreDictionary ( LOCALIZED_CONTENT_STATE_KEY , this . localizedContent ) ;
state . RestoreStringBuilder ( FINAL_LUA_CODE_STATE_KEY , this . finalLuaCode ) ;
}
2025-04-26 16:55:23 +00:00
#region Overrides of AssistantBase < SettingsDialogI18N >
protected override async Task OnInitializedAsync ( )
{
await base . OnInitializedAsync ( ) ;
2026-07-03 13:02:20 +00:00
if ( this . HasAssistantSession )
return ;
2025-04-26 16:55:23 +00:00
await this . OnLanguagePluginChanged ( this . selectedLanguagePluginId ) ;
}
#endregion
2026-07-03 13:02:20 +00:00
private string SystemPromptLanguage ( ) = > this . activeSystemPromptLanguage ? ? ( this . selectedTargetLanguage switch
2025-04-26 16:55:23 +00:00
{
CommonLanguages . OTHER = > this . customTargetLanguage ,
_ = > $"{this.selectedTargetLanguage.Name()}" ,
2026-07-03 13:02:20 +00:00
} ) ;
2025-04-26 16:55:23 +00:00
private async Task OnLanguagePluginChanged ( Guid pluginId )
{
2026-07-03 13:02:20 +00:00
if ( this . IsProcessing )
return ;
2025-04-26 16:55:23 +00:00
this . selectedLanguagePluginId = pluginId ;
await this . OnChangedLanguage ( ) ;
}
private async Task OnChangedLanguage ( )
{
2026-07-03 13:02:20 +00:00
if ( this . IsProcessing )
return ;
2025-04-26 16:55:23 +00:00
this . finalLuaCode . Clear ( ) ;
this . localizedContent . Clear ( ) ;
this . localizationPossible = false ;
if ( PluginFactory . RunningPlugins . FirstOrDefault ( n = > n is PluginLanguage & & n . Id = = this . selectedLanguagePluginId ) is not PluginLanguage comparisonPlugin )
{
2025-05-28 21:14:05 +00:00
this . loadingIssue = string . Format ( T ( "Was not able to load the language plugin for comparison ({0}). Please select a valid, loaded & running language plugin." ) , this . selectedLanguagePluginId ) ;
2025-04-26 16:55:23 +00:00
this . selectedLanguagePlugin = null ;
}
else if ( comparisonPlugin . IETFTag ! = this . selectedTargetLanguage . ToIETFTag ( ) )
{
2025-05-28 21:14:05 +00:00
this . loadingIssue = string . Format ( T ( "The selected language plugin for comparison uses the IETF tag '{0}' which does not match the selected target language '{1}'. Please select a valid, loaded & running language plugin which matches the target language." ) , comparisonPlugin . IETFTag , this . selectedTargetLanguage . ToIETFTag ( ) ) ;
2025-04-26 16:55:23 +00:00
this . selectedLanguagePlugin = null ;
}
else
{
this . selectedLanguagePlugin = comparisonPlugin ;
this . loadingIssue = string . Empty ;
await this . LoadData ( ) ;
}
this . StateHasChanged ( ) ;
}
private async Task LoadData ( )
{
if ( this . selectedLanguagePlugin is null )
{
2025-05-28 21:14:05 +00:00
this . loadingIssue = T ( "Please select a language plugin for comparison." ) ;
2025-04-26 16:55:23 +00:00
this . localizationPossible = false ;
this . isLoading = false ;
this . StateHasChanged ( ) ;
return ;
}
this . isLoading = true ;
this . StateHasChanged ( ) ;
//
// Read the file `Assistants\I18N\allTexts.lua`:
//
#if DEBUG
var filePath = Path . Join ( Environment . CurrentDirectory , "Assistants" , "I18N" ) ;
var resourceFileProvider = new PhysicalFileProvider ( filePath ) ;
#else
2025-05-01 11:23:57 +00:00
var resourceFileProvider = new ManifestEmbeddedFileProvider ( Assembly . GetAssembly ( type : typeof ( Program ) ) ! , "Assistants/I18N" ) ;
2025-04-26 16:55:23 +00:00
#endif
var file = resourceFileProvider . GetFileInfo ( "allTexts.lua" ) ;
await using var fileStream = file . CreateReadStream ( ) ;
using var reader = new StreamReader ( fileStream ) ;
var newI18NDataLuaCode = await reader . ReadToEndAsync ( ) ;
//
// Next, we try to load the text as a language plugin -- without
// actually starting the plugin:
//
var newI18NPlugin = await PluginFactory . Load ( null , newI18NDataLuaCode ) ;
switch ( newI18NPlugin )
{
case NoPlugin noPlugin when noPlugin . Issues . Any ( ) :
this . loadingIssue = noPlugin . Issues . First ( ) ;
break ;
case NoPlugin :
2025-05-28 21:14:05 +00:00
this . loadingIssue = T ( "Was not able to load the I18N plugin. Please check the plugin code." ) ;
2025-04-26 16:55:23 +00:00
break ;
case { IsValid : false } plugin when plugin . Issues . Any ( ) :
this . loadingIssue = plugin . Issues . First ( ) ;
break ;
case PluginLanguage pluginLanguage :
this . loadingIssue = string . Empty ;
var newI18NContent = pluginLanguage . Content ;
var currentI18NContent = this . selectedLanguagePlugin . Content ;
this . addedContent = newI18NContent . ExceptBy ( currentI18NContent . Keys , n = > n . Key ) . ToDictionary ( ) ;
this . removedContent = currentI18NContent . ExceptBy ( newI18NContent . Keys , n = > n . Key ) . ToDictionary ( ) ;
this . localizationPossible = true ;
break ;
}
this . isLoading = false ;
this . StateHasChanged ( ) ;
}
private bool FilterFunc ( KeyValuePair < string , string > element )
{
if ( string . IsNullOrWhiteSpace ( this . searchString ) )
return true ;
if ( element . Key . Contains ( this . searchString , StringComparison . OrdinalIgnoreCase ) )
return true ;
if ( element . Value . Contains ( this . searchString , StringComparison . OrdinalIgnoreCase ) )
return true ;
return false ;
}
private string? ValidatingTargetLanguage ( CommonLanguages language )
{
if ( language = = CommonLanguages . AS_IS )
2025-05-28 21:14:05 +00:00
return T ( "Please select a target language." ) ;
2025-04-26 16:55:23 +00:00
return null ;
}
private string? ValidateCustomLanguage ( string language )
{
if ( this . selectedTargetLanguage = = CommonLanguages . OTHER & & string . IsNullOrWhiteSpace ( language ) )
2025-05-28 21:14:05 +00:00
return T ( "Please provide a custom language." ) ;
2025-04-26 16:55:23 +00:00
return null ;
}
private int NumTotalItems = > ( this . selectedLanguagePlugin ? . Content . Count ? ? 0 ) + this . addedContent . Count - this . removedContent . Count ;
2026-07-03 13:02:20 +00:00
/// <summary>
/// Gets a stable row snapshot for the added-content table.
/// </summary>
private KeyValuePair < string , string > [ ] AddedContentRows = > this . addedContent . ToArray ( ) ;
/// <summary>
/// Gets a stable row snapshot for the removed-content table.
/// </summary>
private KeyValuePair < string , string > [ ] RemovedContentRows = > this . removedContent . ToArray ( ) ;
/// <summary>
/// Gets a stable row snapshot for the localized-content table.
/// </summary>
private KeyValuePair < string , string > [ ] LocalizedContentRows = > this . localizedContent . ToArray ( ) ;
2025-05-28 21:14:05 +00:00
private string AddedContentText = > string . Format ( T ( "Added Content ({0} entries)" ) , this . addedContent . Count ) ;
private string RemovedContentText = > string . Format ( T ( "Removed Content ({0} entries)" ) , this . removedContent . Count ) ;
private string LocalizedContentText = > string . Format ( T ( "Localized Content ({0} entries of {1})" ) , this . localizedContent . Count , this . NumTotalItems ) ;
2025-04-26 16:55:23 +00:00
private async Task LocalizeTextContent ( )
{
2026-04-16 14:48:47 +00:00
await this . Form ! . Validate ( ) ;
if ( ! this . InputIsValid )
2025-04-26 16:55:23 +00:00
return ;
if ( this . selectedLanguagePlugin is null )
return ;
if ( this . selectedLanguagePlugin . IETFTag ! = this . selectedTargetLanguage . ToIETFTag ( ) )
return ;
2026-07-03 13:02:20 +00:00
var addedContentSnapshot = this . addedContent . ToArray ( ) ;
var removedContentSnapshot = this . removedContent . ToArray ( ) ;
var removedContentKeys = removedContentSnapshot . Select ( keyValuePair = > keyValuePair . Key ) . ToHashSet ( StringComparer . Ordinal ) ;
var selectedLanguageContentSnapshot = this . selectedLanguagePlugin . Content . ToArray ( ) ;
var baseLanguageContentSnapshot = PluginFactory . BaseLanguage . Content . ToArray ( ) ;
2025-04-26 16:55:23 +00:00
this . localizedContent . Clear ( ) ;
2026-07-03 13:02:20 +00:00
this . activeSystemPromptLanguage = this . SystemPromptLanguage ( ) ;
try
2025-04-26 16:55:23 +00:00
{
2026-07-03 13:02:20 +00:00
if ( this . selectedTargetLanguage is not CommonLanguages . EN_US )
{
// Phase 1: Translate added content
await this . Phase1TranslateAddedContent ( addedContentSnapshot ) ;
}
else
{
// Case: no translation needed
this . localizedContent = addedContentSnapshot . ToDictionary ( keyValuePair = > keyValuePair . Key , keyValuePair = > keyValuePair . Value , StringComparer . Ordinal ) ;
}
2025-04-26 16:55:23 +00:00
2026-07-03 13:02:20 +00:00
if ( this . CancellationTokenSource ! . IsCancellationRequested )
return ;
2025-04-26 16:55:23 +00:00
2026-07-03 13:02:20 +00:00
//
// Now, we have localized the added content. Next, we must merge
// the localized content with the existing content. However, we
// must skip the removed content. We use the localizedContent
// dictionary for the final result:
//
foreach ( var keyValuePair in selectedLanguageContentSnapshot )
{
if ( this . CancellationTokenSource ! . IsCancellationRequested )
break ;
if ( this . localizedContent . ContainsKey ( keyValuePair . Key ) )
continue ;
if ( removedContentKeys . Contains ( keyValuePair . Key ) )
continue ;
this . localizedContent . Add ( keyValuePair . Key , keyValuePair . Value ) ;
}
if ( this . CancellationTokenSource ! . IsCancellationRequested )
return ;
2025-04-26 16:55:23 +00:00
2026-07-03 13:02:20 +00:00
//
// Phase 2: Create the Lua code. We want to use the base language
// for the comments, though:
//
var commentContent = addedContentSnapshot . ToDictionary ( keyValuePair = > keyValuePair . Key , keyValuePair = > keyValuePair . Value , StringComparer . Ordinal ) ;
foreach ( var keyValuePair in baseLanguageContentSnapshot )
{
if ( this . CancellationTokenSource ! . IsCancellationRequested )
break ;
if ( removedContentKeys . Contains ( keyValuePair . Key ) )
continue ;
commentContent . TryAdd ( keyValuePair . Key , keyValuePair . Value ) ;
}
2025-04-26 16:55:23 +00:00
2026-07-03 13:02:20 +00:00
this . Phase2CreateLuaCode ( commentContent ) ;
2025-04-26 16:55:23 +00:00
}
2026-07-03 13:02:20 +00:00
finally
2025-04-27 14:13:15 +00:00
{
2026-07-03 13:02:20 +00:00
this . activeSystemPromptLanguage = null ;
2025-04-27 14:13:15 +00:00
}
2025-04-26 16:55:23 +00:00
}
2026-07-03 13:02:20 +00:00
/// <summary>
/// Translates the added text content from a stable snapshot.
/// </summary>
/// <param name="addedContentSnapshot">The added text entries captured when the job started.</param>
/// <returns>A task that completes when all added text entries were translated or cancellation was requested.</returns>
private async Task Phase1TranslateAddedContent ( KeyValuePair < string , string > [ ] addedContentSnapshot )
2025-04-26 16:55:23 +00:00
{
var stopwatch = new Stopwatch ( ) ;
var minimumTime = TimeSpan . FromMilliseconds ( 500 ) ;
2026-07-03 13:02:20 +00:00
foreach ( var keyValuePair in addedContentSnapshot )
2025-04-26 16:55:23 +00:00
{
2026-04-16 14:48:47 +00:00
if ( this . CancellationTokenSource ! . IsCancellationRequested )
2025-04-26 16:55:23 +00:00
break ;
//
// We measure the time for each translation.
// We do not want to make more than 120 requests
// per minute, i.e., 2 requests per second.
//
stopwatch . Reset ( ) ;
stopwatch . Start ( ) ;
//
// Translate one text at a time:
//
this . CreateChatThread ( ) ;
var time = this . AddUserRequest ( keyValuePair . Value ) ;
this . localizedContent . Add ( keyValuePair . Key , await this . AddAIResponseAsync ( time ) ) ;
2026-04-16 14:48:47 +00:00
if ( this . CancellationTokenSource ! . IsCancellationRequested )
2025-04-26 16:55:23 +00:00
break ;
//
// Ensure that we do not exceed the rate limit of 2 requests per second:
//
stopwatch . Stop ( ) ;
if ( stopwatch . Elapsed < minimumTime )
await Task . Delay ( minimumTime - stopwatch . Elapsed ) ;
}
}
2025-04-27 14:13:15 +00:00
private void Phase2CreateLuaCode ( IReadOnlyDictionary < string , string > commentContent )
2025-04-26 16:55:23 +00:00
{
this . finalLuaCode . Clear ( ) ;
2026-04-16 14:48:47 +00:00
LuaTable . Create ( ref this . finalLuaCode , "UI_TEXT_CONTENT" , this . localizedContent , commentContent , this . CancellationTokenSource ! . Token ) ;
2025-12-16 17:37:57 +00:00
2025-04-27 07:06:05 +00:00
// Next, we must remove the `root::` prefix from the keys:
this . finalLuaCode . Replace ( "" "UI_TEXT_CONTENT[" root : : "" ", " ""
UI_TEXT_CONTENT [ "
"" ");
2025-04-26 16:55:23 +00:00
}
2025-12-16 17:37:57 +00:00
#if DEBUG
private async Task WriteToPluginFile ( )
{
if ( this . selectedLanguagePlugin is null )
{
this . Snackbar . Add ( T ( "No language plugin selected." ) , Severity . Error ) ;
return ;
}
if ( this . finalLuaCode . Length = = 0 )
{
this . Snackbar . Add ( T ( "No Lua code generated yet." ) , Severity . Error ) ;
return ;
}
try
{
// Determine the plugin file path based on the selected language plugin:
var pluginDirectory = Path . Join ( Environment . CurrentDirectory , "Plugins" , "languages" ) ;
var pluginId = this . selectedLanguagePluginId . ToString ( ) ;
var ietfTag = this . selectedLanguagePlugin . IETFTag . ToLowerInvariant ( ) ;
var pluginFolderName = $"{ietfTag}-{pluginId}" ;
var pluginFilePath = Path . Join ( pluginDirectory , pluginFolderName , "plugin.lua" ) ;
if ( ! File . Exists ( pluginFilePath ) )
{
this . Logger . LogError ( "Plugin file not found: {PluginFilePath}." , pluginFilePath ) ;
this . Snackbar . Add ( T ( "Plugin file not found." ) , Severity . Error ) ;
return ;
}
// Read the existing plugin file:
var existingContent = await File . ReadAllTextAsync ( pluginFilePath ) ;
// Find the position of "UI_TEXT_CONTENT = {}":
const string MARKER = "UI_TEXT_CONTENT = {}" ;
var markerIndex = existingContent . IndexOf ( MARKER , StringComparison . Ordinal ) ;
if ( markerIndex = = - 1 )
{
this . Logger . LogError ( "Could not find 'UI_TEXT_CONTENT = {{}}' marker in plugin file: {PluginFilePath}" , pluginFilePath ) ;
this . Snackbar . Add ( T ( "Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file." ) , Severity . Error ) ;
return ;
}
// Keep everything before the marker and replace everything from the marker onwards:
var metadataSection = existingContent [ . . markerIndex ] ;
var newContent = metadataSection + this . finalLuaCode ;
// Write the updated content back to the file:
await File . WriteAllTextAsync ( pluginFilePath , newContent ) ;
this . Snackbar . Add ( T ( "Successfully updated plugin file." ) , Severity . Success ) ;
}
catch ( Exception ex )
{
this . Logger . LogError ( ex , "Error writing to plugin file." ) ;
this . Snackbar . Add ( T ( "Error writing to plugin file." ) , Severity . Error ) ;
}
}
#endif
2025-04-26 16:55:23 +00:00
}