Added a translation service that will update the base language conversions for each supported language.
197 lines
6.3 KiB
C#
197 lines
6.3 KiB
C#
// 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();
|
|
} |