diff --git a/GetGitDir/GetGitDir.csproj b/GetGitDir/GetGitDir.csproj
new file mode 100644
index 0000000..cf3fec2
--- /dev/null
+++ b/GetGitDir/GetGitDir.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+ embedded
+
+
+
+ embedded
+
+
+
diff --git a/GetGitDir/GetGitDir.exe b/GetGitDir/GetGitDir.exe
new file mode 100644
index 0000000..d15b7d1
Binary files /dev/null and b/GetGitDir/GetGitDir.exe differ
diff --git a/GetGitDir/GetGitDir.sln b/GetGitDir/GetGitDir.sln
new file mode 100644
index 0000000..20bb073
--- /dev/null
+++ b/GetGitDir/GetGitDir.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34511.84
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetGitDir", "GetGitDir.csproj", "{13C3220F-81E4-46E1-9D45-BFE51D425C7A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {13C3220F-81E4-46E1-9D45-BFE51D425C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {13C3220F-81E4-46E1-9D45-BFE51D425C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13C3220F-81E4-46E1-9D45-BFE51D425C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {13C3220F-81E4-46E1-9D45-BFE51D425C7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F57CB7EB-01A8-4A5D-ABDF-F6A655B99AE5}
+ EndGlobalSection
+EndGlobal
diff --git a/GetGitDir/Program.cs b/GetGitDir/Program.cs
new file mode 100644
index 0000000..1b65f30
--- /dev/null
+++ b/GetGitDir/Program.cs
@@ -0,0 +1,20 @@
+try
+{
+ if (!new FileInfo(".git").Exists)
+ return 1;
+
+ var content = File.ReadAllLines(".git");
+
+ if (!content.Any(x => x.StartsWith("gitdir: ")))
+ return 1;
+
+ var gitDirLine = content.First(x => x.StartsWith("gitdir: "));
+
+ Console.WriteLine(gitDirLine[8..]);
+ return 0;
+
+}
+catch (Exception)
+{
+ return 1;
+}
\ No newline at end of file
diff --git a/TranslationSourceGenerator/GlobalSuppressions.cs b/TranslationSourceGenerator/GlobalSuppressions.cs
new file mode 100644
index 0000000..ea6a637
--- /dev/null
+++ b/TranslationSourceGenerator/GlobalSuppressions.cs
@@ -0,0 +1,9 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "member", Target = "~P:TranslationSourceGenerator.Log._logger")]
+[assembly: SuppressMessage("GeneratedRegex", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "")]
diff --git a/TranslationSourceGenerator/Log.cs b/TranslationSourceGenerator/Log.cs
new file mode 100644
index 0000000..ade0837
--- /dev/null
+++ b/TranslationSourceGenerator/Log.cs
@@ -0,0 +1,15 @@
+// Project Makoto
+// Copyright (C) 2023 Fortunevale
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+
+namespace TranslationSourceGenerator;
+
+internal class Log
+{
+ internal static LoggerClient _logger { get; set; }
+}
diff --git a/TranslationSourceGenerator/Program.cs b/TranslationSourceGenerator/Program.cs
new file mode 100644
index 0000000..0cc9cb6
--- /dev/null
+++ b/TranslationSourceGenerator/Program.cs
@@ -0,0 +1,295 @@
+// Project Makoto
+// Copyright (C) 2023 Fortunevale
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+
+global using Xorog.Logger;
+global using Xorog.UniversalExtensions;
+global using Xorog.UniversalExtensions.Entities;
+global using static TranslationSourceGenerator.Log;
+global using static Xorog.UniversalExtensions.UniversalExtensions;
+global using Newtonsoft.Json;
+using System.Reflection;
+using Newtonsoft.Json.Linq;
+using System.Text.RegularExpressions;
+
+namespace TranslationSourceGenerator;
+
+internal class Program
+{
+ public string MakotoSourceOrigin => $@"
+// Project Makoto
+// Copyright (C) 2023 Fortunevale
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+
+namespace {this.Namespace};
+
+#pragma warning disable CS8981
+#pragma warning disable CS8618
+#pragma warning disable IDE1006
+public class Translations : ITranslations
+{{
+ public Dictionary Progress = new();
+ public CommandTranslation[] CommandList {{ get; set; }}
+
+
+ #region AutoGenerated
+ // InsertPoint
+ #endregion AutoGenerated
+}}
+";
+
+ public string StringsJson { get; set; }
+
+ public string TranslationCs { get; set; }
+
+ public string Namespace { get; set; }
+
+ static void Main(string[] args)
+ => new Program().Execute(args).GetAwaiter().GetResult();
+
+ public async Task Execute(string[] args)
+ {
+ if (!Directory.Exists($"{new FileInfo(Assembly.GetExecutingAssembly().FullName).Directory.FullName}/logs"))
+ _ = Directory.CreateDirectory($"{new FileInfo(Assembly.GetExecutingAssembly().FullName).Directory.FullName}/logs");
+
+ _logger = LoggerClient.StartLogger($"{new FileInfo(Assembly.GetExecutingAssembly().FullName).Directory.FullName}/logs/{DateTime.UtcNow:dd-MM-yyyy_HH-mm-ss}.log", CustomLogLevel.Debug, DateTime.UtcNow.AddDays(-3), false);
+
+ _ = Task.Run(async () =>
+ {
+ if (!File.Exists(args.Length > 0 ? args[0] : "e252zru2rjb2rtb23jbrj2bthj2bthjb2jtb4jbtb2jtb4hj"))
+ {
+ _logger.LogError("Translation json expected as arg0");
+ return;
+ }
+
+ if (!File.Exists(args.Length > 1 ? args[1] : "e252zru2rjb2rtb23jbrj2bthj2bthjb2jtb4jbtb2jtb4hj"))
+ {
+ _logger.LogError("Translation json expected as arg1");
+ return;
+ }
+
+ if (args.Length < 2)
+ {
+ _logger.LogError("Namespace expected as arg2");
+ return;
+ }
+
+ this.StringsJson = args[0];
+ this.TranslationCs = args[1];
+ this.Namespace = args[2];
+
+ _logger.LogDebug("Project Makoto Translation Source Generator started up.");
+ _logger.LogDebug("Strings.json Location: {0}", Path.GetFullPath(this.StringsJson));
+ _logger.LogDebug("Translation.cs Location: {0}", Path.GetFullPath(this.TranslationCs));
+ DateTime lastModify = new();
+ while (true)
+ {
+ try
+ {
+ Dictionary> addedFields = new();
+
+ FileInfo fileInfo = new(this.StringsJson);
+
+ if (lastModify != fileInfo.LastWriteTimeUtc)
+ {
+ try
+ {
+ List Warnings = new();
+ var Insert = "";
+
+ _logger.LogDebug("Translation file updated. Updating Translations.cs..");
+
+ var jsonFile = (JObject)JsonConvert.DeserializeObject(File.ReadAllText(this.StringsJson));
+
+ string CreateValidValueName(string str)
+ => Regex.Replace(str, @"[^a-zA-Z0-9]", "_");
+
+ void RecursiveHandle(JObject token, string ParentPath, int depth)
+ {
+ foreach (var item in token)
+ {
+ var className = CreateValidValueName($"{item.Key.First().ToString().ToLower()}{item.Key.Remove(0, 1)}");
+
+ switch (className)
+ {
+ case "object":
+ className = $"@{className}";
+ break;
+ default:
+ break;
+ }
+
+ var fieldName = CreateValidValueName(item.Key.FirstLetterToUpper());
+ var entryPoint = $"{ParentPath}/{className}";
+ var IndexPath = $"// {ParentPath} InsertPoint";
+
+ var InsertPosition = 0;
+ if (!ParentPath.IsNullOrWhiteSpace())
+ {
+ InsertPosition = Insert.IndexOf(IndexPath) + IndexPath.Length;
+ }
+
+ if (!addedFields.ContainsKey(IndexPath))
+ addedFields.Add(IndexPath, new());
+
+ switch (item.Value.Type)
+ {
+ case JTokenType.Object:
+ {
+ var containsLocaleCode = false;
+ var localeCodeIsArray = false;
+
+ List> tokens = new();
+ foreach (var subItem in item.Value.ToObject())
+ {
+ tokens.Add(subItem);
+
+ if (subItem.Key == "en")
+ {
+ containsLocaleCode = true;
+ localeCodeIsArray = subItem.Value.Type == JTokenType.Array;
+ }
+ }
+
+ if (containsLocaleCode)
+ {
+ if (!localeCodeIsArray)
+ {
+ var line = $"{new string(' ', depth * 4)}public SingleTranslationKey {CreateValidValueName(item.Key)};";
+
+ if (addedFields[IndexPath].Contains(line))
+ continue;
+
+ _logger.LogDebug("Found SingleKey '{0}'", item.Key);
+ Insert = Insert.Insert(InsertPosition, $"\n{line}");
+
+ addedFields[IndexPath].Add(line);
+
+ if (!tokens.Any(x => x.Key == "de"))
+ Warnings.Add($"String at path {entryPoint} has no de translation");
+ }
+ else
+ {
+ var line = $"{new string(' ', depth * 4)}public MultiTranslationKey {CreateValidValueName(item.Key)};";
+
+ if (addedFields[IndexPath].Contains(line))
+ continue;
+
+ _logger.LogDebug("Found MultiKey '{0}'", item.Key);
+ Insert = Insert.Insert(InsertPosition, $"\n{line}");
+
+ addedFields[IndexPath].Add(line);
+
+ if (!tokens.Any(x => x.Key == "de"))
+ Warnings.Add($"String at path {entryPoint} has no de translation");
+ }
+ continue;
+ }
+ else
+ {
+ _logger.LogDebug("Found Group '{0}'", item.Key);
+
+ var line = $"\n{new string(' ', depth * 4)}public {className} {fieldName};\n" +
+ $"{new string(' ', depth * 4)}public sealed class {className}\n" +
+ $"{new string(' ', depth * 4)}{{\n" +
+ $"{new string(' ', depth * 4)}// {entryPoint} InsertPoint\n" +
+ $"{new string(' ', depth * 4)}}}\n";
+
+ if (addedFields[IndexPath].Contains(line))
+ break;
+
+ Insert = Insert.Insert(InsertPosition, line);
+ addedFields[IndexPath].Add(line);
+ }
+
+ RecursiveHandle(item.Value.ToObject(), entryPoint, depth + 1);
+ break;
+ }
+ case JTokenType.Integer:
+ {
+ _logger.LogDebug("Found Int '{0}'", item.Key);
+
+ var line = $"\n{new string(' ', depth * 4)}public int {CreateValidValueName(item.Key)};";
+
+ if (addedFields[IndexPath].Contains(line))
+ continue;
+
+ Insert = Insert.Insert(InsertPosition, line);
+ addedFields[IndexPath].Add(line);
+ break;
+ }
+ case JTokenType.Array:
+ {
+ _logger.LogDebug("Found Array '{0}'", item.Key);
+
+ var line = $"\n{new string(' ', depth * 4)}public {className}[] {fieldName};\n" +
+ $"{new string(' ', depth * 4)}public sealed class {className}\n" +
+ $"{new string(' ', depth * 4)}{{\n" +
+ $"{new string(' ', depth * 4)}// {entryPoint} InsertPoint\n" +
+ $"{new string(' ', depth * 4)}}}\n";
+
+ if (fieldName == "CommandList" && IndexPath == "// InsertPoint")
+ {
+ continue;
+ }
+
+ if (!addedFields[IndexPath].Contains(line))
+ {
+ Insert = Insert.Insert(InsertPosition, line);
+ addedFields[IndexPath].Add(line);
+ }
+
+ foreach (var b in item.Value.ToObject())
+ RecursiveHandle(b.ToObject(), entryPoint, depth + 1);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ RecursiveHandle(jsonFile, "", 1);
+ foreach (var b in Warnings)
+ {
+ _logger.LogWarn(b);
+ }
+
+ _logger.LogWarn("Source Generation finished with {Count} warnings.", Warnings.Count);
+
+ Insert = string.Join("\n", Insert.Split("\n").Where(x => !x.Contains("InsertPoint")));
+
+ File.WriteAllText(this.TranslationCs, string.Join("\n", this.MakotoSourceOrigin.Replace("// InsertPoint", Insert).ReplaceLineEndings("\n").Split("\n", StringSplitOptions.RemoveEmptyEntries).Where(x => !x.IsNullOrWhiteSpace())));
+
+ _logger.LogDebug("Updated Translations.cs.");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to update Translations.cs.", ex);
+ }
+ }
+
+ lastModify = fileInfo.LastWriteTimeUtc;
+
+ await Task.Delay(1000);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to watch file", ex);
+ await Task.Delay(10000);
+ }
+ }
+ });
+
+ await Task.Delay(-1);
+ }
+}
diff --git a/TranslationSourceGenerator/Properties/launchSettings.json b/TranslationSourceGenerator/Properties/launchSettings.json
new file mode 100644
index 0000000..ce5e3c2
--- /dev/null
+++ b/TranslationSourceGenerator/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "TranslationSourceGenerator": {
+ "commandName": "Project",
+ "commandLineArgs": "\"C:\\Users\\Xorog\\Documents\\GitHub\\ProjectIchigo\\ProjectMakoto\\Translations\\strings.json\" \"C:\\Users\\Xorog\\Documents\\GitHub\\ProjectIchigo\\ProjectMakoto\\Entities\\Translation\\Translations.cs\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/TranslationSourceGenerator/RunDefault.sh b/TranslationSourceGenerator/RunDefault.sh
new file mode 100644
index 0000000..2160252
--- /dev/null
+++ b/TranslationSourceGenerator/RunDefault.sh
@@ -0,0 +1 @@
+dotnet run -- ../../ProjectMakoto/Translations/strings.json ../../ProjectMakoto/Entities/Translation/Translations.cs ProjectMakoto.Entities
\ No newline at end of file
diff --git a/TranslationSourceGenerator/TranslationSourceGenerator.csproj b/TranslationSourceGenerator/TranslationSourceGenerator.csproj
new file mode 100644
index 0000000..c340171
--- /dev/null
+++ b/TranslationSourceGenerator/TranslationSourceGenerator.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ annotations
+ x64
+
+
+
+
+
+
+
+
diff --git a/TranslationSourceGenerator/TranslationSourceGenerator.sln b/TranslationSourceGenerator/TranslationSourceGenerator.sln
new file mode 100644
index 0000000..4206f54
--- /dev/null
+++ b/TranslationSourceGenerator/TranslationSourceGenerator.sln
@@ -0,0 +1,44 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34309.116
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranslationSourceGenerator", "TranslationSourceGenerator.csproj", "{1962E64F-BFA8-4ECA-83C6-FD09959D7E98}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xorog.Logger", "..\..\Dependencies\Xorog.Logger\Xorog.Logger.csproj", "{8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xorog.UniversalExtensions", "..\..\Dependencies\Xorog.UniversalExtensions\Xorog.UniversalExtensions.csproj", "{1082AEE5-F298-412D-BE0D-12253D25AF1C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Release|x64 = Release|x64
+ x64|x64 = x64|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1962E64F-BFA8-4ECA-83C6-FD09959D7E98}.Debug|x64.ActiveCfg = Debug|x64
+ {1962E64F-BFA8-4ECA-83C6-FD09959D7E98}.Debug|x64.Build.0 = Debug|x64
+ {1962E64F-BFA8-4ECA-83C6-FD09959D7E98}.Release|x64.ActiveCfg = Release|x64
+ {1962E64F-BFA8-4ECA-83C6-FD09959D7E98}.Release|x64.Build.0 = Release|x64
+ {1962E64F-BFA8-4ECA-83C6-FD09959D7E98}.x64|x64.ActiveCfg = Debug|x64
+ {1962E64F-BFA8-4ECA-83C6-FD09959D7E98}.x64|x64.Build.0 = Debug|x64
+ {8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}.Debug|x64.ActiveCfg = Debug|x64
+ {8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}.Debug|x64.Build.0 = Debug|x64
+ {8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}.Release|x64.ActiveCfg = Release|x64
+ {8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}.Release|x64.Build.0 = Release|x64
+ {8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}.x64|x64.ActiveCfg = x64|x64
+ {8A8883E2-C01C-48A5-9CFF-16DB013DA0C8}.x64|x64.Build.0 = x64|x64
+ {1082AEE5-F298-412D-BE0D-12253D25AF1C}.Debug|x64.ActiveCfg = Debug|x64
+ {1082AEE5-F298-412D-BE0D-12253D25AF1C}.Debug|x64.Build.0 = Debug|x64
+ {1082AEE5-F298-412D-BE0D-12253D25AF1C}.Release|x64.ActiveCfg = Release|x64
+ {1082AEE5-F298-412D-BE0D-12253D25AF1C}.Release|x64.Build.0 = Release|x64
+ {1082AEE5-F298-412D-BE0D-12253D25AF1C}.x64|x64.ActiveCfg = x64|x64
+ {1082AEE5-F298-412D-BE0D-12253D25AF1C}.x64|x64.Build.0 = x64|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0B29991B-405D-41AD-8AAA-56C1A701ED99}
+ EndGlobalSection
+EndGlobal