Replaced IFailedLoopback with IOUtcome
Added a translation service that will update the base language conversions for each supported language.
This commit is contained in:
parent
f4831b93b9
commit
052b833dd6
11
e-suite.Workflow.Core/Enums/ApprovalVerdict.cs
Normal file
11
e-suite.Workflow.Core/Enums/ApprovalVerdict.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace e_suite.Workflow.Core.Enums;
|
||||||
|
|
||||||
|
public enum ApprovalVerdict
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Pending,
|
||||||
|
Approved,
|
||||||
|
ApprovedWithComments,
|
||||||
|
Rejected,
|
||||||
|
Reviewed
|
||||||
|
}
|
||||||
12
e-suite.Workflow.Core/Interfaces/IOutcome.cs
Normal file
12
e-suite.Workflow.Core/Interfaces/IOutcome.cs
Normal 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; }
|
||||||
|
}
|
||||||
197
eSuite.Translator/Program.cs
Normal file
197
eSuite.Translator/Program.cs
Normal 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();
|
||||||
|
}
|
||||||
41
eSuite.Translator/TranslationService.cs
Normal file
41
eSuite.Translator/TranslationService.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
eSuite.Translator/eSuite.Translator.csproj
Normal file
10
eSuite.Translator/eSuite.Translator.csproj
Normal 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>
|
||||||
Loading…
Reference in New Issue
Block a user