Skip to content

Commit 8103001

Browse files
Added I18N assistant for localization of AI Studio content (#422)
1 parent 201fb25 commit 8103001

28 files changed

+749
-157
lines changed

app/Build/Commands/CollectI18NKeysCommand.cs

Lines changed: 58 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public async Task CollectI18NKeys()
5959
Console.WriteLine($" {counter:###,###} files processed, {allI18NContent.Count:###,###} keys found.");
6060

6161
Console.Write("- Creating Lua code ...");
62-
var luaCode = this.ExportToLuaTable(allI18NContent);
62+
var luaCode = this.ExportToLuaAssignments(allI18NContent);
6363

6464
// Build the path, where we want to store the Lua code:
6565
var luaPath = Path.Join(cwd, "Assistants", "I18N", "allTexts.lua");
@@ -69,134 +69,68 @@ public async Task CollectI18NKeys()
6969

7070
Console.WriteLine(" done.");
7171
}
72-
73-
private string ExportToLuaTable(Dictionary<string, string> keyValuePairs)
72+
73+
private string ExportToLuaAssignments(Dictionary<string, string> keyValuePairs)
7474
{
75-
// Collect all nodes:
76-
var root = new Dictionary<string, object>();
77-
78-
//
79-
// Split all collected keys into nodes:
80-
//
81-
foreach (var key in keyValuePairs.Keys.Order())
82-
{
83-
var path = key.Split('.');
84-
var current = root;
85-
for (var i = 0; i < path.Length - 1; i++)
86-
{
87-
// We ignore the AISTUDIO segment of the path:
88-
if(path[i] == "AISTUDIO")
89-
continue;
90-
91-
if (!current.TryGetValue(path[i], out var child) || child is not Dictionary<string, object> childDict)
92-
{
93-
childDict = new Dictionary<string, object>();
94-
current[path[i]] = childDict;
95-
}
96-
97-
current = childDict;
98-
}
99-
100-
current[path.Last()] = keyValuePairs[key];
101-
}
75+
var sb = new StringBuilder();
10276

103-
//
104-
// Inner method to build Lua code from the collected nodes:
105-
//
106-
void ToLuaTable(StringBuilder sb, Dictionary<string, object> innerDict, int indent = 0)
107-
{
108-
sb.AppendLine("{");
109-
var prefix = new string(' ', indent * 4);
110-
foreach (var kvp in innerDict)
111-
{
112-
if (kvp.Value is Dictionary<string, object> childDict)
113-
{
114-
sb.Append($"{prefix} {kvp.Key}");
115-
sb.Append(" = ");
116-
117-
ToLuaTable(sb, childDict, indent + 1);
118-
}
119-
else if (kvp.Value is string s)
120-
{
121-
sb.AppendLine($"{prefix} -- {s.Trim().Replace("\n", " ")}");
122-
sb.Append($"{prefix} {kvp.Key}");
123-
sb.Append(" = ");
124-
sb.Append($"""
125-
"{s}"
126-
""");
127-
sb.AppendLine(",");
128-
sb.AppendLine();
129-
}
130-
}
77+
// Add the mandatory plugin metadata:
78+
sb.AppendLine(
79+
"""
80+
-- The ID for this plugin:
81+
ID = "77c2688a-a68f-45cc-820e-fa8f3038a146"
13182
132-
sb.AppendLine(prefix + "},");
133-
sb.AppendLine();
134-
}
135-
136-
//
137-
// Write the Lua code:
138-
//
139-
var sbLua = new StringBuilder();
140-
141-
// To make the later parsing easier, we add the mandatory plugin
142-
// metadata:
143-
sbLua.AppendLine(
144-
"""
145-
-- The ID for this plugin:
146-
ID = "77c2688a-a68f-45cc-820e-fa8f3038a146"
147-
148-
-- The icon for the plugin:
149-
ICON_SVG = ""
150-
151-
-- The name of the plugin:
152-
NAME = "Collected I18N keys"
153-
154-
-- The description of the plugin:
155-
DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project."
156-
157-
-- The version of the plugin:
158-
VERSION = "1.0.0"
159-
160-
-- The type of the plugin:
161-
TYPE = "LANGUAGE"
162-
163-
-- The authors of the plugin:
164-
AUTHORS = {"MindWork AI Community"}
165-
166-
-- The support contact for the plugin:
167-
SUPPORT_CONTACT = "MindWork AI Community"
168-
169-
-- The source URL for the plugin:
170-
SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio"
171-
172-
-- The categories for the plugin:
173-
CATEGORIES = { "CORE" }
174-
175-
-- The target groups for the plugin:
176-
TARGET_GROUPS = { "EVERYONE" }
177-
178-
-- The flag for whether the plugin is maintained:
179-
IS_MAINTAINED = true
180-
181-
-- When the plugin is deprecated, this message will be shown to users:
182-
DEPRECATION_MESSAGE = ""
183-
184-
-- The IETF BCP 47 tag for the language. It's the ISO 639 language
185-
-- code followed by the ISO 3166-1 country code:
186-
IETF_TAG = "en-US"
187-
188-
-- The language name in the user's language:
189-
LANG_NAME = "English (United States)"
190-
191-
""");
192-
193-
sbLua.Append("UI_TEXT_CONTENT = ");
194-
if(root["UI_TEXT_CONTENT"] is Dictionary<string, object> dict)
195-
ToLuaTable(sbLua, dict);
83+
-- The icon for the plugin:
84+
ICON_SVG = ""
85+
86+
-- The name of the plugin:
87+
NAME = "Collected I18N keys"
88+
89+
-- The description of the plugin:
90+
DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project."
91+
92+
-- The version of the plugin:
93+
VERSION = "1.0.0"
94+
95+
-- The type of the plugin:
96+
TYPE = "LANGUAGE"
97+
98+
-- The authors of the plugin:
99+
AUTHORS = {"MindWork AI Community"}
100+
101+
-- The support contact for the plugin:
102+
SUPPORT_CONTACT = "MindWork AI Community"
103+
104+
-- The source URL for the plugin:
105+
SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio"
106+
107+
-- The categories for the plugin:
108+
CATEGORIES = { "CORE" }
109+
110+
-- The target groups for the plugin:
111+
TARGET_GROUPS = { "EVERYONE" }
112+
113+
-- The flag for whether the plugin is maintained:
114+
IS_MAINTAINED = true
115+
116+
-- When the plugin is deprecated, this message will be shown to users:
117+
DEPRECATION_MESSAGE = ""
118+
119+
-- The IETF BCP 47 tag for the language. It's the ISO 639 language
120+
-- code followed by the ISO 3166-1 country code:
121+
IETF_TAG = "en-US"
122+
123+
-- The language name in the user's language:
124+
LANG_NAME = "English (United States)"
125+
126+
"""
127+
);
196128

197-
return sbLua.ToString();
129+
// Add the UI_TEXT_CONTENT table:
130+
LuaTable.Create(ref sb, "UI_TEXT_CONTENT", keyValuePairs);
131+
return sb.ToString();
198132
}
199-
133+
200134
private List<string> FindAllTextTags(ReadOnlySpan<char> fileContent)
201135
{
202136
const string START_TAG = """

app/MindWork AI Studio.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RID/@EntryIndexedValue">RID</s:String>
1414
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
1515
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String>
16+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=I18N/@EntryIndexedValue">I18N</s:String>
1617
<s:Boolean x:Key="/Default/UserDictionary/Words/=agentic/@EntryIndexedValue">True</s:Boolean>
1718
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
1819
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>

app/MindWork AI Studio/Assistants/AssistantBase.razor

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
</CascadingValue>
3030

3131
<MudStack Row="true" AlignItems="AlignItems.Center" StretchItems="StretchItems.Start" Class="mb-3">
32-
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" OnClick="() => this.SubmitAction()" Style="@this.SubmitButtonStyle">
32+
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" OnClick="async () => await this.Start()" Style="@this.SubmitButtonStyle">
3333
@this.SubmitText
3434
</MudButton>
3535
@if (this.isProcessing && this.cancellationTokenSource is not null)
@@ -97,14 +97,14 @@
9797
{
9898
case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
9999
<MudTooltip Text="@buttonData.Tooltip">
100-
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
100+
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" Disabled="@buttonData.DisabledAction()" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
101101
@buttonData.Text
102102
</MudButton>
103103
</MudTooltip>
104104
break;
105105

106106
case ButtonData buttonData:
107-
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
107+
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" Disabled="@buttonData.DisabledAction()" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
108108
@buttonData.Text
109109
</MudButton>
110110
break;

app/MindWork AI Studio/Assistants/AssistantBase.razor.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase, IMe
9797
protected Profile currentProfile = Profile.NO_PROFILE;
9898
protected ChatThread? chatThread;
9999
protected IContent? lastUserPrompt;
100+
protected CancellationTokenSource? cancellationTokenSource;
100101

101102
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
102-
103-
private CancellationTokenSource? cancellationTokenSource;
103+
104104
private ContentBlock? resultingContentBlock;
105105
private string[] inputIssues = [];
106106
private bool isProcessing;
@@ -179,6 +179,16 @@ public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEv
179179
return null;
180180
}
181181

182+
private async Task Start()
183+
{
184+
using (this.cancellationTokenSource = new())
185+
{
186+
await this.SubmitAction();
187+
}
188+
189+
this.cancellationTokenSource = null;
190+
}
191+
182192
private void TriggerFormChange(FormFieldChangedEventArgs _)
183193
{
184194
this.formChangeTimer.Stop();
@@ -286,16 +296,12 @@ protected async Task<string> AddAIResponseAsync(DateTimeOffset time, bool hideCo
286296

287297
this.isProcessing = true;
288298
this.StateHasChanged();
289-
290-
using (this.cancellationTokenSource = new())
291-
{
292-
// Use the selected provider to get the AI response.
293-
// By awaiting this line, we wait for the entire
294-
// content to be streamed.
295-
this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread, this.cancellationTokenSource.Token);
296-
}
297-
298-
this.cancellationTokenSource = null;
299+
300+
// Use the selected provider to get the AI response.
301+
// By awaiting this line, we wait for the entire
302+
// content to be streamed.
303+
this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread, this.cancellationTokenSource!.Token);
304+
299305
this.isProcessing = false;
300306
this.StateHasChanged();
301307

0 commit comments

Comments
 (0)