Skip to content

Create I18N data, part 1 #404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6158ec7
Create Lua code from collected I18N keys and texts
SommerEngineering Apr 17, 2025
7fb5220
Added the I18N collection process to the run configuration
SommerEngineering Apr 17, 2025
722b014
Fixed *.razor.cs file handling
SommerEngineering Apr 17, 2025
71c87ec
Added I18N
SommerEngineering Apr 17, 2025
cb0ed0f
Added exception for T methods
SommerEngineering Apr 17, 2025
a671963
Remove unused code and redundant imports.
SommerEngineering Apr 17, 2025
610ae1c
Refactor ApplyFilters to improve parameter clarity.
SommerEngineering Apr 17, 2025
06fcaf7
Fixed optional message handling
SommerEngineering Apr 17, 2025
6e0deb4
Refactor Settings component to inherit from MSGComponentBase
SommerEngineering Apr 17, 2025
8914e20
Refactor Assistants to inherit from MSGComponentBase
SommerEngineering Apr 17, 2025
6867393
Upgrade Supporters component to inherit MSGComponentBase.
SommerEngineering Apr 17, 2025
36df88a
Fix the logger type in MSGComponentBase to use the correct class.
SommerEngineering Apr 17, 2025
2caf291
Handle missing or empty translations in MSGComponentBase.
SommerEngineering Apr 17, 2025
088e4ad
Refactor for localization support using translation helpers.
SommerEngineering Apr 17, 2025
19888ff
Add language plugin support to MainLayout.
SommerEngineering Apr 17, 2025
8ff66f8
Refactor translation logic into ILangExtensions utility.
SommerEngineering Apr 17, 2025
46e1e70
Refactor to use localization (T) for text and labels.
SommerEngineering Apr 17, 2025
c5360eb
Remove unused ComponentName overrides across the codebase.
SommerEngineering Apr 17, 2025
087ab52
Refactor SettingsDialogBase to inherit from MSGComponentBase
SommerEngineering Apr 17, 2025
fd25f14
Refactor Plugins component to streamline message handling
SommerEngineering Apr 17, 2025
d85e3da
Add I18N Lua resource and assistant folder to the project structure
SommerEngineering Apr 17, 2025
2dcbbc6
Add localization support to the settings dialogs.
SommerEngineering Apr 17, 2025
9039a39
Fix the incorrect property name for LabelOff in ConfigurationOption.
SommerEngineering Apr 20, 2025
573abd4
Change log level from warning to debug for missing keys
SommerEngineering Apr 21, 2025
4df4056
Merged changes from main
SommerEngineering Apr 24, 2025
dec1492
Formatting
SommerEngineering Apr 24, 2025
167eb13
Merge branch 'main' into extract-i18n
SommerEngineering Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/.run/Collect I18N content.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Collect I18N content" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="dotnet run collect-i18n" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/Build" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/Build" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/opt/homebrew/bin/nu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
4 changes: 3 additions & 1 deletion app/.run/Tauri Dev.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Collect I18N content" run_configuration_type="ShConfigurationType" />
</method>
</configuration>
</component>
150 changes: 144 additions & 6 deletions app/Build/Commands/CollectI18NKeysCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public async Task CollectI18NKeys()
var wwwrootPath = Path.Join(cwd, "wwwroot");
var allFiles = Directory.EnumerateFiles(cwd, "*", SearchOption.AllDirectories);
var counter = 0;
var sb = new StringBuilder();

var allI18NContent = new Dictionary<string, string>();
foreach (var filePath in allFiles)
{
counter++;
Expand All @@ -46,17 +46,155 @@ public async Task CollectI18NKeys()

var ns = this.DetermineNamespace(filePath);
var fileInfo = new FileInfo(filePath);
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty);
var langNamespace = $"{ns}::{name}".ToUpperInvariant().Replace(".", "::");
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty).Replace(".razor", string.Empty);
var langNamespace = $"{ns}.{name}".ToUpperInvariant();
foreach (var match in matches)
{
var key = $"root::{langNamespace}::T{match.ToFNV32()}";
// The key in the format A.B.C.D.T{hash}:
var key = $"UI_TEXT_CONTENT.{langNamespace}.T{match.ToFNV32()}";
allI18NContent.TryAdd(key, match);
}
}

Console.WriteLine($" {counter:###,###} files processed, {allI18NContent.Count:###,###} keys found.");

Console.Write("- Creating Lua code ...");
var luaCode = this.ExportToLuaTable(allI18NContent);

// Build the path, where we want to store the Lua code:
var luaPath = Path.Join(cwd, "Assistants", "I18N", "allTexts.lua");

// Store the Lua code:
await File.WriteAllTextAsync(luaPath, luaCode, Encoding.UTF8);

Console.WriteLine(" done.");
}

private string ExportToLuaTable(Dictionary<string, string> keyValuePairs)
{
// Collect all nodes:
var root = new Dictionary<string, object>();

//
// Split all collected keys into nodes:
//
foreach (var key in keyValuePairs.Keys.Order())
{
var path = key.Split('.');
var current = root;
for (var i = 0; i < path.Length - 1; i++)
{
// We ignore the AISTUDIO segment of the path:
if(path[i] == "AISTUDIO")
continue;

if (!current.TryGetValue(path[i], out var child) || child is not Dictionary<string, object> childDict)
{
childDict = new Dictionary<string, object>();
current[path[i]] = childDict;
}

current = childDict;
}

current[path.Last()] = keyValuePairs[key];
}

//
// Inner method to build Lua code from the collected nodes:
//
void ToLuaTable(StringBuilder sb, Dictionary<string, object> innerDict, int indent = 0)
{
sb.AppendLine("{");
var prefix = new string(' ', indent * 4);
foreach (var kvp in innerDict)
{
if (kvp.Value is Dictionary<string, object> childDict)
{
sb.Append($"{prefix} {kvp.Key}");
sb.Append(" = ");

ToLuaTable(sb, childDict, indent + 1);
}
else if (kvp.Value is string s)
{
sb.AppendLine($"{prefix} -- {s.Trim().Replace("\n", " ")}");
sb.Append($"{prefix} {kvp.Key}");
sb.Append(" = ");
sb.Append($"""
"{s}"
""");
sb.AppendLine(",");
sb.AppendLine();
}
}

sb.AppendLine(prefix + "},");
sb.AppendLine();
}

//
// Write the Lua code:
//
var sbLua = new StringBuilder();

// To make the later parsing easier, we add the mandatory plugin
// metadata:
sbLua.AppendLine(
"""
-- The ID for this plugin:
ID = "77c2688a-a68f-45cc-820e-fa8f3038a146"

-- The icon for the plugin:
ICON_SVG = ""

-- The name of the plugin:
NAME = "Collected I18N keys"

-- The description of the plugin:
DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project."

-- The version of the plugin:
VERSION = "1.0.0"

-- The type of the plugin:
TYPE = "LANGUAGE"

-- The authors of the plugin:
AUTHORS = {"MindWork AI Community"}

-- The support contact for the plugin:
SUPPORT_CONTACT = "MindWork AI Community"

-- The source URL for the plugin:
SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio"

-- The categories for the plugin:
CATEGORIES = { "CORE" }

-- The target groups for the plugin:
TARGET_GROUPS = { "EVERYONE" }

-- The flag for whether the plugin is maintained:
IS_MAINTAINED = true

-- When the plugin is deprecated, this message will be shown to users:
DEPRECATION_MESSAGE = ""

-- The IETF BCP 47 tag for the language. It's the ISO 639 language
-- code followed by the ISO 3166-1 country code:
IETF_TAG = "en-US"

-- The language name in the user's language:
LANG_NAME = "English (United States)"

""");

sbLua.Append("UI_TEXT_CONTENT = ");
if(root["UI_TEXT_CONTENT"] is Dictionary<string, object> dict)
ToLuaTable(sbLua, dict);

Console.WriteLine($" {counter:###,###} files processed.");
Console.WriteLine();
return sbLua.ToString();
}

private List<string> FindAllTextTags(ReadOnlySpan<char> fileContent)
Expand Down
2 changes: 0 additions & 2 deletions app/Build/Commands/UpdateWebAssetsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global

using Build.Tools;

using SharedTools;

namespace Build.Commands;
Expand Down
Loading