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