using System.Text; using DataModel.Database; namespace Processor.Generators; public class DotnetBigFile : IGenerator { private static readonly List CULTURE_CODES = new(); private static int DEFAULT_CULTURE_INDEX = -1; public async Task> GenerateAsync() { const string filename = "I18N.cs"; var destPath = await AppSettings.GetGeneratorDotnetDestinationPath(); destPath = Environment.ExpandEnvironmentVariables(destPath); long destBytesWritten = 0; var pathFinal = Path.Join(destPath, filename); var pathTemp = Path.Join(destPath, filename + ".gen"); var issueFinal = string.Empty; try { if(File.Exists(pathTemp)) File.Delete(pathTemp); } catch (IOException e) { return new ProcessorResult(0, false, $"Cannot delete the temporary file: '{e.Message}'. Hint: Is the ransomware protection enabled in your Windows system? If so, please make sure that the I18N Commander has write permission."); } CULTURE_CODES.Clear(); var cultures = await AppSettings.GetCultureInfos(); foreach (var (code, _) in cultures) CULTURE_CODES.Add(code); DEFAULT_CULTURE_INDEX = await AppSettings.GetGeneratorDotnetDefaultCultureIndex(); DEFAULT_CULTURE_INDEX -= 1; // 1-based to 0-based try { await using var fileStream = new FileStream(pathTemp, FileMode.CreateNew, FileAccess.Write, FileShare.None); await using var writer = new StreamWriter(fileStream, Encoding.UTF8); await writer.WriteLineAsync("""using System.Globalization;"""); await writer.WriteLineAsync($"""namespace {await AppSettings.GetGeneratorDotnetNamespace()};"""); await this.CreateStaticClass(writer, "I18N", 0, async (streamWriter, indention) => { var indentionString = this.AddIndention(indention); var buildTime = DateTime.UtcNow; await writer.WriteLineAsync($"{indentionString}public static readonly string BUILD_TIME = \"{buildTime:yyyy.MM.dd HH:mm:ss}\";"); await writer.WriteLineAsync($"{indentionString}public static readonly long BUILD_TIME_TICKS = {buildTime.Ticks};"); // Go through the first layer of sections: var sections = await SectionProcessor.LoadLayer(0); foreach (var section in sections) await this.TransformSection(writer, indention, section); }); } catch (IOException e) { // Happens, e.g. when the ransomware protection on Windows is active and // the I18N commander is not on the exclusion list. return new ProcessorResult(0, false, $"Cannot write the generator's result file: '{e.Message}'. Hint: Is the ransomware protection enabled in your Windows system? If so, please make sure that the I18N Commander has write permission."); } finally { if (File.Exists(pathTemp)) { destBytesWritten = new FileInfo(pathTemp).Length; if (destBytesWritten > 0) { try { if (File.Exists(pathFinal)) File.Delete(pathFinal); File.Move(pathTemp, pathFinal); } catch (IOException e) { // Happens when the file is still in use by the compiler, the IDE, etc. // Depends on the timing, this happens sometimes. We ignore it, though. issueFinal = e.Message; } } } } if(string.IsNullOrWhiteSpace(issueFinal)) return new ProcessorResult(destBytesWritten, true, string.Empty); else return new ProcessorResult(0, false, $"Cannot move the generator's result file to the destination: '{issueFinal}'. Hint: Is the ransomware protection enabled in your Windows system? If so, please make sure that the I18N Commander has write permission."); } private string AddIndention(int indention) => new string(' ', indention * 3); private async Task TransformSection(TextWriter writer, int indention, Section section) { await this.CreateStaticClass(writer, section.DataKey, indention, async (_, innerIndention) => { var textElements = section.TextElements; foreach (var textElement in textElements) await this.TransformTextElement(writer, innerIndention, textElement); var childSections = await SectionProcessor.GetChildSections(section.DataKey); foreach (var childSection in childSections) await this.TransformSection(writer, innerIndention, childSection); }); } private async Task TransformTextElement(TextWriter writer, int indention, TextElement textElement) { var indentionString = this.AddIndention(indention); var indentionInner1 = this.AddIndention(indention + 1); var indentionInner2 = this.AddIndention(indention + 2); await writer.WriteLineAsync($"""{indentionString}public static string E_{textElement.Code}() => E_{textElement.Code}(CultureInfo.CurrentCulture);"""); await writer.WriteLineAsync($"""{indentionString}public static string E_{textElement.Code}(CultureInfo culture)"""); await writer.WriteLineAsync($$"""{{indentionString}}{"""); // opening { // Write the default culture's text: var defaultCultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[DEFAULT_CULTURE_INDEX]); var defaultCultureText = defaultCultureTranslation?.Text ?? string.Empty; await this.WriteTextContent(writer, indentionInner1, textElement.IsMultiLine, defaultCultureText, variableName: "defaultText"); await writer.WriteLineAsync($"""{indentionInner1}var name = culture.Name;"""); await writer.WriteLineAsync($"""{indentionInner1}if(name.Length < 2)"""); await writer.WriteLineAsync($$"""{{indentionInner1}}{"""); // opening { await writer.WriteLineAsync($"""{indentionInner2}return defaultText;"""); await writer.WriteLineAsync($$"""{{indentionInner1}}}"""); // closing } for (var cultureIndex = 0; cultureIndex < CULTURE_CODES.Count; cultureIndex++) { await writer.WriteLineAsync($"""{indentionInner1}if ({this.CreateIf(CULTURE_CODES[cultureIndex])})"""); await writer.WriteLineAsync($$"""{{indentionInner1}}{"""); // opening { if(cultureIndex == DEFAULT_CULTURE_INDEX) await writer.WriteLineAsync($"""{indentionInner2}return defaultText;"""); else { var cultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[cultureIndex]); var cultureText = cultureTranslation?.Text ?? string.Empty; await this.WriteTextContent(writer, indentionInner2, textElement.IsMultiLine, cultureText); await writer.WriteLineAsync($"""{indentionInner2}return text;"""); } await writer.WriteLineAsync($$"""{{indentionInner1}}}"""); // closing } } // Add the default case: await writer.WriteLineAsync($"""{indentionInner1}return defaultText;"""); await writer.WriteLineAsync($$"""{{indentionString}}}"""); // closing } await writer.WriteLineAsync(); } private async Task CreateStaticClass(TextWriter writer, string name, int indention, Func content) { var indentionString = this.AddIndention(indention); await writer.WriteLineAsync(indentionString); await writer.WriteLineAsync($"{indentionString}public static class {name}"); await writer.WriteLineAsync($"{indentionString}{{"); await content(writer, indention + 1); await writer.WriteLineAsync($"{indentionString}}}"); } private async Task WriteTextContent(TextWriter writer, string indention, bool isMultiline, string content, string variableName = "text") { if(isMultiline) { await writer.WriteLineAsync($""""""""" {indention}const string {variableName} = """""""" """""""""); await writer.WriteLineAsync(content); await writer.WriteLineAsync($""""""""" """"""""; """""""""); } else await writer.WriteLineAsync($"""""""""{indention}const string {variableName} = """"""""{content}"""""""";"""""""""); } private string CreateIf(string cultureCode) { if (string.IsNullOrWhiteSpace(cultureCode)) return "false"; var sb = new StringBuilder(); var len = cultureCode.Length; if (len > 2) sb.Append($"""name.Length >= {len} && """); for (var i = 0; i < cultureCode.Length; i++) { sb.Append($"""name[{i}] is '{cultureCode[i]}'"""); // When this is not the last character, we need to add " && ": if (i < cultureCode.Length - 1) sb.Append(" && "); } return sb.ToString(); } }