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($"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};"); await writer.WriteLineAsync(); await writer.WriteLineAsync($"{indentionString}private static int PREVIOUS_CULTURE = -1;"); // 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 indentionPropString = this.AddIndention(indention + 1); var indentionPropInner1String = this.AddIndention(indention + 2); var indentionPropInner2String = this.AddIndention(indention + 3); var indentionPropInner3String = this.AddIndention(indention + 4); await writer.WriteLineAsync($"{indentionString}private static string E_{textElement.Code}_CACHE = \"\";"); await writer.WriteLineAsync($"{indentionString}public static string E_{textElement.Code}"); await writer.WriteLineAsync($"{indentionString}{{"); await writer.WriteLineAsync($"{indentionPropString}get"); await writer.WriteLineAsync($"{indentionPropString}{{"); await writer.WriteLineAsync($"{indentionPropInner1String}var currentCulture = CultureInfo.CurrentCulture.Name;"); await writer.WriteLineAsync($"{indentionPropInner1String}if(PREVIOUS_CULTURE == currentCulture.GetHashCode())"); await writer.WriteLineAsync($"{indentionPropInner2String}return E_{textElement.Code}_CACHE;"); await writer.WriteLineAsync($"{indentionPropInner1String}else"); await writer.WriteLineAsync($"{indentionPropInner1String}{{"); await writer.WriteLineAsync($"{indentionPropInner2String}PREVIOUS_CULTURE = currentCulture.GetHashCode();"); for (var cultureIndex = 0; cultureIndex < CULTURE_CODES.Count; cultureIndex++) { if(cultureIndex == 0) await writer.WriteLineAsync($"{indentionPropInner2String}if (currentCulture.StartsWith(\"{CULTURE_CODES[cultureIndex]}\", StringComparison.InvariantCultureIgnoreCase))"); else await writer.WriteLineAsync($"{indentionPropInner2String}else if (currentCulture.StartsWith(\"{CULTURE_CODES[cultureIndex]}\", StringComparison.InvariantCultureIgnoreCase))"); await writer.WriteLineAsync($"{indentionPropInner2String}{{"); var cultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[cultureIndex]); var cultureText = cultureTranslation?.Text ?? string.Empty; await writer.WriteLineAsync($"{indentionPropInner3String}var text = @\"{Utils.MadeVerbatimStringLiteral(cultureText)}\";"); await writer.WriteLineAsync($"{indentionPropInner3String}E_{textElement.Code}_CACHE = text;"); await writer.WriteLineAsync($"{indentionPropInner3String}return text;"); await writer.WriteLineAsync($"{indentionPropInner2String}}}"); } // Add the default case: await writer.WriteLineAsync($"{indentionPropInner2String}else"); await writer.WriteLineAsync($"{indentionPropInner2String}{{"); var defaultCultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[DEFAULT_CULTURE_INDEX]); var defaultCultureText = defaultCultureTranslation?.Text ?? string.Empty; await writer.WriteLineAsync($"{indentionPropInner3String}var text = @\"{Utils.MadeVerbatimStringLiteral(defaultCultureText)}\";"); await writer.WriteLineAsync($"{indentionPropInner3String}E_{textElement.Code}_CACHE = text;"); await writer.WriteLineAsync($"{indentionPropInner3String}return text;"); await writer.WriteLineAsync($"{indentionPropInner2String}}}"); await writer.WriteLineAsync($"{indentionPropInner1String}}}"); await writer.WriteLineAsync($"{indentionPropString}}}"); await writer.WriteLineAsync($"{indentionString}}}"); 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}}}"); } }