using System.Text; namespace Processor.Generators; public class GodotCSV : IGenerator { private static readonly List CULTURE_CODES = new(); #region Implementation of IGenerator /// public async Task> GenerateAsync() { const string filename = "I18N.csv"; const char delimiter = ','; var destPath = await AppSettings.GetGeneratorGodotDestinationPath(); 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.OrderBy(n => n.Code)) CULTURE_CODES.Add(code); if (CULTURE_CODES.Count == 0) return new ProcessorResult(0, true, string.Empty); try { await using var fileStream = new FileStream(pathTemp, FileMode.CreateNew, FileAccess.Write, FileShare.None); await using var writer = new StreamWriter(fileStream, Encoding.UTF8); // Write the header: await writer.WriteLineAsync($"keys{delimiter}{string.Join(delimiter, CULTURE_CODES)}"); // // Iterate through all sections and text elements: // var sectionDepth = await SectionProcessor.GetDepth(); foreach (var sectionLayer in Enumerable.Range(0, sectionDepth + 1)) { var sections = await SectionProcessor.LoadLayer(sectionLayer); foreach (var section in sections.OrderBy(n => n.DataKey)) foreach (var textElement in section.TextElements.OrderBy(n => n.Code)) { // Get the key for this text element: var key = await TextElementProcessor.GetKey(textElement.Id); // Write the path to the text element as key: await writer.WriteAsync($"{key}{delimiter}"); // Load all translations: var translations = textElement.Translations.DistinctBy(n => n.Culture).ToDictionary(translation => translation.Culture, translation => translation.Text); // Iterate through all text elements and write the translations out: for (var cultureIndex = 0; cultureIndex < CULTURE_CODES.Count; cultureIndex++) { var cultureCode = CULTURE_CODES[cultureIndex]; var cultureText = translations.TryGetValue(cultureCode, out var text) ? text : string.Empty; // Write the text with delimiter or only the text in case of the last column: if (cultureIndex < CULTURE_CODES.Count - 1) await writer.WriteAsync($@"""{Escape4CSV(cultureText)}""{delimiter}"); else await writer.WriteLineAsync($@"""{Escape4CSV(cultureText)}"""); } } } } 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 static string Escape4CSV(string text) { if (string.IsNullOrWhiteSpace(text)) return string.Empty; var sb = new StringBuilder(text.Length); foreach (var c in text) { if (c == '"') sb.Append(@""""""); // quoted double quote, i.e. prints "" else sb.Append(c); } return sb.ToString(); } #endregion }