Client LuaCsForBarotrauma
LocalizationCSVtoXML.cs
1 #if DEBUG
2 using Barotrauma.IO;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Text;
7 
8 namespace Barotrauma
9 {
10  class LocalizationCSVtoXML
11  {
12  private static readonly List<int> conversationClosingIndent = new List<int>();
13  private static readonly char[] separator = new char[1] { '|' };
14 
15  private const string conversationsPath = "Content/NPCConversations";
16  private const string infoTextPath = "Content/Texts";
17  private const string xmlHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
18 
19  private static readonly string[,] translatedLanguageNames = new string[13, 2] { { "English", "English" }, { "French", "Français" }, { "German", "Deutsch" },
20  { "Russian", "Русский" }, { "Brazilian Portuguese", "Português brasileiro" }, { "Simplified Chinese", "中文(简体)" }, { "Traditional Chinese", "中文(繁體)" },
21  { "Castilian Spanish", "Castellano" }, { "Latinamerican Spanish", "Español Latinoamericano" }, { "Polish", "Polski" }, { "Turkish", "Türkçe" },
22  { "Japanese", "日本語" }, { "Korean", "한국어" } };
23 
24  public static void ConvertMasterLocalizationKit(string outputTextsDirectory, string outputConversationsDirectory, bool convertConversations)
25  {
26  List<string> languages = new List<string>();
27  for (int i = 0; i < 2; i++)
28  {
29  string textFilePath;
30  string outputFileName;
31  switch (i)
32  {
33  case 0:
34  textFilePath = Path.Combine(infoTextPath, "Texts.csv");
35  outputFileName = "Vanilla.xml";
36  break;
37  case 1:
38  textFilePath = Path.Combine(infoTextPath, "EditorTexts.csv");
39  outputFileName = "VanillaEditorTexts.xml";
40  break;
41  default:
42  throw new NotImplementedException();
43  }
44 
45  Dictionary<string, List<string>> xmlContent;
46  try
47  {
48  xmlContent = ConvertInfoTextToXML(File.ReadAllLines(textFilePath, Encoding.UTF8));
49  }
50  catch (Exception e)
51  {
52  DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath, e);
53  return;
54  }
55  if (xmlContent == null)
56  {
57  DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath);
58  return;
59  }
60  foreach (string language in xmlContent.Keys)
61  {
62  languages.Add(language);
63  string languageNoWhitespace = language.Replace(" ", "");
64  string xmlFileFullPath = Path.Combine(outputTextsDirectory, $"{languageNoWhitespace}/{languageNoWhitespace}{outputFileName}");
65  File.WriteAllLines(xmlFileFullPath, xmlContent[language], Encoding.UTF8);
66  DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath);
67  }
68  }
69 
70  if (convertConversations)
71  {
72  string conversationFilePath = Path.Combine(infoTextPath, "NPCConversations.csv");
73  var conversationLinesAll = File.ReadAllLines(conversationFilePath, Encoding.UTF8);
74  foreach (string language in languages)
75  {
76  List<string> convXmlContent = ConvertConversationsToXML(conversationLinesAll, language);
77  if (convXmlContent == null)
78  {
79  DebugConsole.ThrowError("NPCConversation Localization .csv to .xml conversion failed for: " + language);
80  continue;
81  }
82  string languageNoWhitespace = language.Replace(" ", "");
83  string xmlFileFullPath = Path.Combine(outputConversationsDirectory, languageNoWhitespace, $"NpcConversations_{languageNoWhitespace}.xml");
84  File.WriteAllLines(xmlFileFullPath, convXmlContent, Encoding.UTF8);
85  DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath);
86  }
87  }
88  }
89 
90  [Obsolete]
91  public static void ConvertIndividualFiles()
92  {
93  if (GameSettings.CurrentConfig.Language != TextManager.DefaultLanguage)
94  {
95  DebugConsole.ThrowError("Use the english localization when converting .csv to allow copying values");
96  return;
97  }
98 
99  List<string> conversationFiles = new List<string>();
100  List<string> infoTextFiles = new List<string>();
101 
102  for (int i = 0; i < translatedLanguageNames.GetUpperBound(0) + 1; i++)
103  {
104  string language = translatedLanguageNames[i, 0];
105  string languageNoWhitespace = language.RemoveWhitespace();
106 
107  if (Directory.Exists(conversationsPath + $"/{languageNoWhitespace}"))
108  {
109  IEnumerable<string> conversationFileArray = Directory.GetFiles(conversationsPath + $"/{languageNoWhitespace}", "*.csv", System.IO.SearchOption.AllDirectories);
110 
111  if (conversationFileArray != null)
112  {
113  foreach (string filePath in conversationFileArray)
114  {
115  conversationFiles.Add(filePath);
116  }
117  }
118  }
119  else
120  {
121  DebugConsole.ThrowError("Directory at: " + conversationsPath + $"/{languageNoWhitespace} does not exist!");
122  }
123 
124  if (Directory.Exists(infoTextPath + $"/{languageNoWhitespace}"))
125  {
126  IEnumerable<string> infoTextFileArray = Directory.GetFiles(infoTextPath + $"/{languageNoWhitespace}", "*.csv", System.IO.SearchOption.AllDirectories);
127 
128  if (infoTextFileArray != null)
129  {
130  foreach (string filePath in infoTextFileArray)
131  {
132  infoTextFiles.Add(filePath);
133  }
134  }
135  }
136  else
137  {
138  DebugConsole.ThrowError("Directory at: " + infoTextPath + $"/{languageNoWhitespace} does not exist!");
139  }
140 
141  for (int j = 0; j < conversationFiles.Count; j++)
142  {
143  List<string> xmlContent = ConvertConversationsToXML(File.ReadAllLines(conversationFiles[j], Encoding.UTF8), language);
144  if (xmlContent == null)
145  {
146  DebugConsole.ThrowError("NPCConversation Localization .csv to .xml conversion failed for: " + conversationFiles[j]);
147  continue;
148  }
149  string xmlFileFullPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}/NpcConversations_{languageNoWhitespace}.xml";
150  File.WriteAllLines(xmlFileFullPath, xmlContent, Encoding.UTF8);
151  DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath);
152  }
153 
154  for (int j = 0; j < infoTextFiles.Count; j++)
155  {
156  List<string> xmlContent;
157  try
158  {
159  xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[j], Encoding.UTF8), language);
160  }
161  catch (Exception e)
162  {
163  DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j], e);
164  continue;
165  }
166  if (xmlContent == null)
167  {
168  DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j]);
169  continue;
170  }
171  string xmlFileFullPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}/{languageNoWhitespace}Vanilla.xml";
172  File.WriteAllLines(xmlFileFullPath, xmlContent, Encoding.UTF8);
173  DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath);
174  }
175 
176  if (conversationFiles.Count == 0 && infoTextFiles.Count == 0)
177  {
178  DebugConsole.ThrowError("No .csv files found to convert for: " + language);
179  continue;
180  }
181 
182  conversationFiles.Clear();
183  infoTextFiles.Clear();
184  }
185  }
186 
187  private static Dictionary<string, List<string>> ConvertInfoTextToXML(string[] csvContent)
188  {
189  Dictionary<string, List<string>> xmlContentByLanguage = new Dictionary<string, List<string>>();
190 
191  //get all the languages from the header row
192  string headerRow = csvContent[0];
193  var headerContent = headerRow.Split(separator);
194  for (int i = 0; i < headerContent.Length; i++)
195  {
196  string languageName = headerContent[i];
197  if (languageName.Equals("tag", StringComparison.OrdinalIgnoreCase) ||
198  languageName.Equals("comments", StringComparison.OrdinalIgnoreCase))
199  {
200  continue;
201  }
202  string translatedName = GetTranslatedName(languageName);
203  bool nowhitespace = TextManager.IsCJK(translatedName);
204  List<string> xmlContent = new List<string>()
205  {
206  xmlHeader,
207  $"<infotexts language=\"{languageName}\" nowhitespace=\"{nowhitespace.ToString().ToLower()}\" translatedname=\"{translatedName}\">"
208  };
209  xmlContentByLanguage.Add(headerContent[i], xmlContent);
210  }
211 
212  for (int row = 1; row < csvContent.Length; row++) // Start at one to ignore header
213  {
214  if (!xmlContentByLanguage.Values.All(values => values.Count == xmlContentByLanguage["English"].Count))
215  {
216  throw new Exception($"Error while converting csv to xml: mismatching number of texts on line {row-1} ({csvContent[row - 1]}). Check that there's no extra newlines, separators or missing lines in the csv file.");
217  }
218 
219  if (csvContent[row].Length == 0)
220  {
221  AddToAllLanguages(string.Empty);
222  }
223  else
224  {
225  string[] split = csvContent[row].Split(separator);
226 
227  if (split.Length < xmlContentByLanguage.Count)
228  {
229  throw new Exception($"Error while converting csv to xml: not enough values on line {row} ({csvContent[row]}). Check that there's no extra newlines, separators or missing lines in the csv file.");
230  }
231 
232  if (split.Length > 1) // Localization data
233  {
234  //all values empty = an empty line
235  if (split.All(s => s.IsNullOrEmpty()))
236  {
237  AddToAllLanguages(string.Empty);
238  }
239  //value is empty in all languages
240  else if (!split[0].IsNullOrEmpty() && split.Skip(2).All(s => s.IsNullOrEmpty()))
241  {
242  //first line is all lower-case and contains dot, assume it's an empty value
243  if (split[0].Contains(".") && !split[0].Any(char.IsUpper))
244  {
245  AddToAllLanguages($"<{split[0]}></{split[0]}>");
246  }
247  //otherwise assume it's a comment
248  else
249  {
250  AddToAllLanguages($"<!-- {split[0]} -->");
251  }
252  }
253  else
254  {
255  for (int j = 0; j < split.Length; j++)
256  {
257  string languageName = headerContent[j];
258  if (languageName.Equals("tag", StringComparison.OrdinalIgnoreCase) ||
259  languageName.Equals("comments", StringComparison.OrdinalIgnoreCase))
260  {
261  continue;
262  }
263  split[j] = split[j].Replace(" & ", " &amp; ");
264  xmlContentByLanguage[languageName].Add($"<{split[0]}>{split[j]}</{split[0]}>");
265  }
266  }
267  }
268  else // A header/comment
269  {
270  AddToAllLanguages($"<!-- {split[0]} -->");
271  }
272  }
273  }
274 
275  AddToAllLanguages(string.Empty);
276  AddToAllLanguages("</infotexts>");
277 
278  void AddToAllLanguages(string str)
279  {
280  foreach (var xmlContent in xmlContentByLanguage.Values)
281  {
282  xmlContent.Add(str);
283  }
284  }
285 
286  return xmlContentByLanguage;
287  }
288 
289  [Obsolete]
290  private static List<string> ConvertInfoTextToXML(string[] csvContent, string language)
291  {
292  List<string> xmlContent = new List<string>
293  {
294  xmlHeader
295  };
296 
297  string translatedName = GetTranslatedName(language);
298  bool nowhitespace = TextManager.IsCJK(translatedName);
299 
300  xmlContent.Add($"<infotexts language=\"{language}\" nowhitespace=\"{nowhitespace.ToString().ToLower()}\" translatedname=\"{translatedName}\">");
301 
302  for (int i = 1; i < csvContent.Length; i++) // Start at one to ignore header
303  {
304  csvContent[i] = csvContent[i].Trim(separator);
305 
306  if (csvContent[i].Length == 0)
307  {
308  xmlContent.Add(string.Empty);
309  }
310  else
311  {
312  string[] split = csvContent[i].Split(separator, 3);
313 
314  if (split.Length >= 2) // Localization data
315  {
316  split[1] = split[1].Replace(" & ", " &amp; ");
317  xmlContent.Add($"<{split[0]}>{split[1]}</{split[0]}>");
318  }
319  else if (split[0].Contains(".") && !split[0].Any(char.IsUpper)) // An empty field
320  {
321  xmlContent.Add($"<{split[0]}></{split[0]}>");
322  }
323  else // A header
324  {
325  xmlContent.Add($"<!-- {split[0]} -->");
326  }
327  }
328  }
329 
330  xmlContent.Add(string.Empty);
331  xmlContent.Add("</infotexts>");
332 
333  return xmlContent;
334  }
335 
336  private static string GetTranslatedName(string language)
337  {
338  for (int i = 0; i < translatedLanguageNames.Length; i++)
339  {
340  if (translatedLanguageNames[i, 0] == language) return translatedLanguageNames[i, 1];
341  }
342 
343  DebugConsole.ThrowError("No translated language name found for " + language);
344  return string.Empty;
345  }
346 
347  private static List<string> ConvertConversationsToXML(string[] csvContent, string language)
348  {
349  List<string> xmlContent = new List<string>
350  {
351  xmlHeader
352  };
353 
354  string translatedName = GetTranslatedName(language);
355  bool nowhitespace = TextManager.IsCJK(translatedName);
356 
357  int languageColumn = -1;
358  string[] headerSplit = csvContent[0].Split(separator);
359  for (int i = 0; i < headerSplit.Length; i++)
360  {
361  if (headerSplit[i] == language ||
362  (language == "English" && headerSplit[i]== "Line (Original)"))
363  {
364  languageColumn = i;
365  break;
366  }
367  }
368 
369  xmlContent.Add($"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\" nowhitespace=\"{nowhitespace}\">");
370 
371  conversationClosingIndent.Clear();
372  int conversationStart = 1;
373 
374  xmlContent.Add(string.Empty);
375 
376  for (int i = conversationStart; i < csvContent.Length; i++) // Conversations
377  {
378  string[] split = csvContent[i].Split(separator);
379  int emptyFields = 0;
380  for (int j = 0; j < split.Length; j++)
381  {
382  if (split[j] == string.Empty) { emptyFields++; }
383  }
384  if (emptyFields == split.Length) // Empty line with only commas, indicates the end of the previous conversation
385  {
386  HandleClosingElements(xmlContent, 0);
387  xmlContent.Add(string.Empty);
388  continue;
389  }
390  else if (emptyFields == split.Length - 1 && split[0] != string.Empty) // A header
391  {
392  xmlContent.Add($"<!-- {split[0]} -->");
393  continue;
394  }
395 
396  string line = split[languageColumn].Replace("\"", "");
397  string speaker = split[2];
398  int depthIndex = int.Parse(split[3]);
399  // 3 = original line
400  string flags = split[4].Replace("\"", "");
401  string allowedJobs = split[5].Replace("\"", "");
402  string speakerTags = split[6].Replace("\"", "");
403  string minIntensity = split[7].Replace("\"", "").Replace(",", ".");
404  string maxIntensity = split[8].Replace("\"", "").Replace(",", ".");
405 
406  string element =
407  $"{GetIndenting(depthIndex)}" +
408  $"<Conversation line=\"{line}\" " +
409  $"{GetVariable("speaker", speaker)}" +
410  $"{GetVariable("flags", flags)}" +
411  $"{GetVariable("allowedjobs", allowedJobs)}" +
412  $"{GetVariable("speakertags", speakerTags)}" +
413  $"{GetVariable("minintensity", minIntensity)}" +
414  $"{GetVariable("maxintensity", maxIntensity)}";
415 
416  bool nextIsSubConvo = false;
417  int nextDepth = 999;
418 
419  if (i < csvContent.Length - 1) // Not at the end of file
420  {
421  string[] nextConversationElement = csvContent[i + 1].Split(separator);
422 
423  if (nextConversationElement[3] != string.Empty)
424  {
425  nextDepth = int.Parse(nextConversationElement[3]);
426  nextIsSubConvo = nextDepth > depthIndex;
427  }
428 
429  if (!nextIsSubConvo)
430  {
431  xmlContent.Add(element.TrimEnd() + "/>");
432  if (nextDepth < depthIndex)
433  {
434  HandleClosingElements(xmlContent, nextDepth);
435  }
436  }
437  else
438  {
439  xmlContent.Add(element.TrimEnd() + ">");
440  conversationClosingIndent.Add(depthIndex);
441  }
442  }
443  else
444  {
445  //end of file, close remaining xml tags
446  xmlContent.Add(element.TrimEnd() + "/>");
447  for (int j = depthIndex - 1; j >= 0; j--)
448  {
449  HandleClosingElements(xmlContent, j);
450  }
451  }
452  }
453 
454  xmlContent.Add(string.Empty);
455  xmlContent.Add("</Conversations>");
456 
457  return xmlContent;
458  }
459 
460  private static void HandleClosingElements(List<string> xmlContent, int targetDepth)
461  {
462  if (conversationClosingIndent.Count == 0) { return; }
463 
464  for (int k = conversationClosingIndent.Count - 1; k >= 0; k--)
465  {
466  int currentIndent = conversationClosingIndent[k];
467  if (currentIndent < targetDepth) { break; }
468  xmlContent.Add($"{GetIndenting(currentIndent)}</Conversation>");
469  conversationClosingIndent.RemoveAt(k);
470  }
471  }
472 
473  private static string GetIndenting(int depthIndex)
474  {
475  string indenting = string.Empty;
476 
477  for (int i = 0; i < depthIndex; i++)
478  {
479  indenting += "\t";
480  }
481 
482  return indenting;
483  }
484 
485  private static string GetVariable(string name, string value)
486  {
487  if (value == string.Empty)
488  {
489  return string.Empty;
490  }
491  else
492  {
493  return $"{name}=\"{value}\" ";
494  }
495  }
496  }
497 }
498 #endif