Replaced IFailedLoopback with IOUtcome

Added a translation service that will update the base language conversions for each supported language.
This commit is contained in:
Colin Dawson 2026-02-22 23:43:57 +00:00
parent f4831b93b9
commit 052b833dd6
5 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,11 @@
namespace e_suite.Workflow.Core.Enums;
public enum ApprovalVerdict
{
None,
Pending,
Approved,
ApprovedWithComments,
Rejected,
Reviewed
}

View File

@ -0,0 +1,12 @@
using e_suite.Workflow.Core.Attributes;
namespace e_suite.Workflow.Core.Interfaces;
[TaskCapability]
public interface IOutcome<T>
{
//Todo runtime only property.
//public T? TaskOutcome { get; set; }
Dictionary<T, Guid> OutcomeActions { get; set; }
}

View File

@ -0,0 +1,197 @@
// See https://aka.ms/new-console-template for more information
using eSuite.Translator;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
var http = new HttpClient
{
Timeout = TimeSpan.FromSeconds(600),
BaseAddress = new Uri("http://aipi.cluster.local:11434")
};
var translator = new OllamaTranslationService(http);
LanguagePack LoadDictionary(string rootpath, string foldername)
{
var path = Path.Combine(rootpath, foldername);
//var files = Directory.GetFiles(path, "*.json", SearchOption.AllDirectories);
var files = Directory
.EnumerateFiles(path)
.Where(f => f.EndsWith(".json", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".lang", StringComparison.OrdinalIgnoreCase));
var languagePack = new LanguagePack();
//load the master files into memory,
foreach (var file in files)
{
if (Path.GetExtension(file).ToLower() == ".lang")
{
languagePack.Locale = Path.GetFileNameWithoutExtension(file);
continue;
}
var relativePath = Path.GetRelativePath(path, file);
var json = File.ReadAllText(file);
var jsonDocument = JsonNode.Parse(json);
languagePack.Files[Path.GetFileName(file)] = jsonDocument;
}
return languagePack;
}
void SaveDictionary(string rootpath, string foldername, Dictionary<string, JsonNode> dictionary)
{
var folderPath = Path.Combine(rootpath, foldername);
foreach (var item in dictionary)
{
var filename = item.Key;
var path = Path.Combine(folderPath, filename);
var sorted = SortJson(item.Value);
File.WriteAllText(path, sorted.ToJsonString(new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}));
}
Console.WriteLine($"{folderPath} saved");
}
async Task ReconcileNode(JsonNode masterNode, JsonNode languageNode, string targetLanguage)
{
// If the master node is a value, nothing to recurse into
if (masterNode is JsonValue)
return;
// If the master node is an object, ensure the language node is also an object
if (masterNode is JsonObject masterObj)
{
var langObj = languageNode as JsonObject
?? throw new InvalidOperationException("Language node must be an object here");
foreach (var kvp in masterObj)
{
var key = kvp.Key;
var masterChild = kvp.Value!;
// If the language is missing this key, create a placeholder
if (!langObj.TryGetPropertyValue(key, out var langChild))
{
// If master is a value → create empty string
if (masterChild is JsonValue)
{
var sourceText = masterChild.ToString();
var translated = await translator.TranslateAsync(sourceText, targetLanguage);
if (!translated.Equals(sourceText, StringComparison.InvariantCultureIgnoreCase))
langChild = JsonValue.Create(translated);
else
langChild = null;
}
else
{
// If master is an object → create empty object
langChild = new JsonObject();
}
if (langChild != null)
langObj[key] = langChild;
}
// Recurse into children
if (masterChild is JsonObject)
await ReconcileNode(masterChild, langChild!, targetLanguage);
}
}
}
async Task ReconcileDictionary(Dictionary<string, JsonNode> languageDictionary, Dictionary<string, JsonNode> masterDictionary, string targetLanguage)
{
foreach (var kvp in masterDictionary)
{
var filename = kvp.Key;
var masterJson = kvp.Value;
// Does the language folder have this file?
if (!languageDictionary.TryGetValue(filename, out var langJson))
{
langJson = new JsonObject();
languageDictionary[filename] = langJson;
}
// Reconcile the tree structure
await ReconcileNode(masterJson, langJson, targetLanguage);
}
}
static JsonNode SortJson(JsonNode node)
{
switch (node)
{
case JsonObject obj:
{
var sorted = new JsonObject();
foreach (var kvp in obj.OrderBy(k => k.Key, StringComparer.Ordinal))
{
// Clone before sorting to avoid parent conflicts
var cloned = kvp.Value?.DeepClone();
sorted[kvp.Key] = SortJson(cloned!);
}
return sorted;
}
case JsonArray arr:
{
var newArr = new JsonArray();
foreach (var item in arr)
{
var cloned = item?.DeepClone();
newArr.Add(SortJson(cloned!));
}
return newArr;
}
default:
return node.DeepClone(); // primitives
}
}
Console.WriteLine("Starting translation reconciliation");
const string rootpath = @"C:\Code\Gitea\e-suite\e-suite.webui\public\locales";
var masterDictionary = LoadDictionary(rootpath, "en");
SaveDictionary(rootpath, "en", masterDictionary.Files);
Console.WriteLine("Master files loaded");
var rootDir = new DirectoryInfo(rootpath);
var folders = rootDir.GetDirectories().Select(x => x.Name ).ToList();
folders.Remove("en"); //Ignore the master folder
var languageFolders = folders.Where(x => !x.Contains("-")).ToList();
var localeFolders = folders.Where(x => x.Contains("-")).ToList();
foreach ( var language in languageFolders)
{
var languageDictionary = LoadDictionary(rootpath, language);
await ReconcileDictionary(languageDictionary.Files, masterDictionary.Files, languageDictionary.Locale);
SaveDictionary(rootpath, language, languageDictionary.Files);
}
Console.WriteLine("Main Language files completed.");
public class LanguagePack
{
public string Locale { get; set; }
public Dictionary<string, JsonNode> Files { get; set; } = new();
}

View File

@ -0,0 +1,41 @@
using System.Net.Http.Json;
using System.Text.Json.Nodes;
namespace eSuite.Translator;
public interface ITranslationService
{
Task<string> TranslateAsync(string sourceText, string targetLanguage);
}
public class OllamaTranslationService : ITranslationService
{
private readonly HttpClient _http;
public OllamaTranslationService(HttpClient http)
{
_http = http;
}
public async Task<string> TranslateAsync(string sourceText, string targetLanguage)
{
Console.Write( $"Translating \"{sourceText}\" into \"{targetLanguage}\": ");
var request = new
{
model = "ZimaBlueAI/HY-MT1.5-1.8:7b",
prompt = $"Translate this text into {targetLanguage}: {sourceText}",
stream = false
};
var response = await _http.PostAsJsonAsync("/api/generate", request);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadFromJsonAsync<JsonNode>();
// Ollama returns streaming chunks; for simplicity assume "response" contains the text
var result = (json?["response"]?.ToString() ?? "").Trim();
Console.WriteLine(result);
return result;
}
}

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>