4 using System.Collections.Generic;
10 class LocalizationCSVtoXML
12 private static readonly List<int> conversationClosingIndent =
new List<int>();
13 private static readonly
char[] separator =
new char[1] {
'|' };
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\"?>";
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",
"한국어" } };
24 public static void ConvertMasterLocalizationKit(
string outputTextsDirectory,
string outputConversationsDirectory,
bool convertConversations)
26 List<string> languages =
new List<string>();
27 for (
int i = 0; i < 2; i++)
30 string outputFileName;
34 textFilePath = Path.Combine(infoTextPath,
"Texts.csv");
35 outputFileName =
"Vanilla.xml";
38 textFilePath = Path.Combine(infoTextPath,
"EditorTexts.csv");
39 outputFileName =
"VanillaEditorTexts.xml";
42 throw new NotImplementedException();
45 Dictionary<string, List<string>> xmlContent;
48 xmlContent = ConvertInfoTextToXML(File.ReadAllLines(textFilePath, Encoding.UTF8));
52 DebugConsole.ThrowError(
"InfoText Localization .csv to .xml conversion failed for: " + textFilePath, e);
55 if (xmlContent ==
null)
57 DebugConsole.ThrowError(
"InfoText Localization .csv to .xml conversion failed for: " + textFilePath);
60 foreach (
string language
in xmlContent.Keys)
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);
70 if (convertConversations)
72 string conversationFilePath = Path.Combine(infoTextPath,
"NPCConversations.csv");
73 var conversationLinesAll = File.ReadAllLines(conversationFilePath, Encoding.UTF8);
74 foreach (
string language
in languages)
76 List<string> convXmlContent = ConvertConversationsToXML(conversationLinesAll, language);
77 if (convXmlContent ==
null)
79 DebugConsole.ThrowError(
"NPCConversation Localization .csv to .xml conversion failed for: " + language);
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);
91 public static void ConvertIndividualFiles()
93 if (GameSettings.CurrentConfig.Language != TextManager.DefaultLanguage)
95 DebugConsole.ThrowError(
"Use the english localization when converting .csv to allow copying values");
99 List<string> conversationFiles =
new List<string>();
100 List<string> infoTextFiles =
new List<string>();
102 for (
int i = 0; i < translatedLanguageNames.GetUpperBound(0) + 1; i++)
104 string language = translatedLanguageNames[i, 0];
105 string languageNoWhitespace = language.RemoveWhitespace();
107 if (Directory.Exists(conversationsPath + $
"/{languageNoWhitespace}"))
109 IEnumerable<string> conversationFileArray = Directory.GetFiles(conversationsPath + $
"/{languageNoWhitespace}",
"*.csv", System.IO.SearchOption.AllDirectories);
111 if (conversationFileArray !=
null)
113 foreach (
string filePath
in conversationFileArray)
115 conversationFiles.Add(filePath);
121 DebugConsole.ThrowError(
"Directory at: " + conversationsPath + $
"/{languageNoWhitespace} does not exist!");
124 if (Directory.Exists(infoTextPath + $
"/{languageNoWhitespace}"))
126 IEnumerable<string> infoTextFileArray = Directory.GetFiles(infoTextPath + $
"/{languageNoWhitespace}",
"*.csv", System.IO.SearchOption.AllDirectories);
128 if (infoTextFileArray !=
null)
130 foreach (
string filePath
in infoTextFileArray)
132 infoTextFiles.Add(filePath);
138 DebugConsole.ThrowError(
"Directory at: " + infoTextPath + $
"/{languageNoWhitespace} does not exist!");
141 for (
int j = 0; j < conversationFiles.Count; j++)
143 List<string> xmlContent = ConvertConversationsToXML(File.ReadAllLines(conversationFiles[j], Encoding.UTF8), language);
144 if (xmlContent ==
null)
146 DebugConsole.ThrowError(
"NPCConversation Localization .csv to .xml conversion failed for: " + conversationFiles[j]);
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);
154 for (
int j = 0; j < infoTextFiles.Count; j++)
156 List<string> xmlContent;
159 xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[j], Encoding.UTF8), language);
163 DebugConsole.ThrowError(
"InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j], e);
166 if (xmlContent ==
null)
168 DebugConsole.ThrowError(
"InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j]);
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);
176 if (conversationFiles.Count == 0 && infoTextFiles.Count == 0)
178 DebugConsole.ThrowError(
"No .csv files found to convert for: " + language);
182 conversationFiles.Clear();
183 infoTextFiles.Clear();
187 private static Dictionary<string, List<string>> ConvertInfoTextToXML(
string[] csvContent)
189 Dictionary<string, List<string>> xmlContentByLanguage =
new Dictionary<string, List<string>>();
192 string headerRow = csvContent[0];
193 var headerContent = headerRow.Split(separator);
194 for (
int i = 0; i < headerContent.Length; i++)
196 string languageName = headerContent[i];
197 if (languageName.Equals(
"tag", StringComparison.OrdinalIgnoreCase) ||
198 languageName.Equals(
"comments", StringComparison.OrdinalIgnoreCase))
202 string translatedName = GetTranslatedName(languageName);
203 bool nowhitespace = TextManager.IsCJK(translatedName);
204 List<string> xmlContent =
new List<string>()
207 $
"<infotexts language=\"{languageName}\" nowhitespace=\"{nowhitespace.ToString().ToLower()}\" translatedname=\"{translatedName}\">"
209 xmlContentByLanguage.Add(headerContent[i], xmlContent);
212 for (
int row = 1; row < csvContent.Length; row++)
214 if (!xmlContentByLanguage.Values.All(values => values.Count == xmlContentByLanguage[
"English"].Count))
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.");
219 if (csvContent[row].Length == 0)
221 AddToAllLanguages(
string.Empty);
225 string[] split = csvContent[row].Split(separator);
227 if (split.Length < xmlContentByLanguage.Count)
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.");
232 if (split.Length > 1)
235 if (split.All(s => s.IsNullOrEmpty()))
237 AddToAllLanguages(
string.Empty);
240 else if (!split[0].IsNullOrEmpty() && split.Skip(2).All(s => s.IsNullOrEmpty()))
243 if (split[0].Contains(
".") && !split[0].
Any(
char.IsUpper))
245 AddToAllLanguages($
"<{split[0]}></{split[0]}>");
250 AddToAllLanguages($
"<!-- {split[0]} -->");
255 for (
int j = 0; j < split.Length; j++)
257 string languageName = headerContent[j];
258 if (languageName.Equals(
"tag", StringComparison.OrdinalIgnoreCase) ||
259 languageName.Equals(
"comments", StringComparison.OrdinalIgnoreCase))
263 split[j] = split[j].Replace(
" & ",
" & ");
264 xmlContentByLanguage[languageName].Add($
"<{split[0]}>{split[j]}</{split[0]}>");
270 AddToAllLanguages($
"<!-- {split[0]} -->");
275 AddToAllLanguages(
string.Empty);
276 AddToAllLanguages(
"</infotexts>");
278 void AddToAllLanguages(
string str)
280 foreach (var xmlContent
in xmlContentByLanguage.Values)
286 return xmlContentByLanguage;
290 private static List<string> ConvertInfoTextToXML(
string[] csvContent,
string language)
292 List<string> xmlContent =
new List<string>
297 string translatedName = GetTranslatedName(language);
298 bool nowhitespace = TextManager.IsCJK(translatedName);
300 xmlContent.Add($
"<infotexts language=\"{language}\" nowhitespace=\"{nowhitespace.ToString().ToLower()}\" translatedname=\"{translatedName}\">");
302 for (
int i = 1; i < csvContent.Length; i++)
304 csvContent[i] = csvContent[i].Trim(separator);
306 if (csvContent[i].Length == 0)
308 xmlContent.Add(
string.Empty);
312 string[] split = csvContent[i].Split(separator, 3);
314 if (split.Length >= 2)
316 split[1] = split[1].Replace(
" & ",
" & ");
317 xmlContent.Add($
"<{split[0]}>{split[1]}</{split[0]}>");
319 else if (split[0].Contains(
".") && !split[0].
Any(
char.IsUpper))
321 xmlContent.Add($
"<{split[0]}></{split[0]}>");
325 xmlContent.Add($
"<!-- {split[0]} -->");
330 xmlContent.Add(
string.Empty);
331 xmlContent.Add(
"</infotexts>");
336 private static string GetTranslatedName(
string language)
338 for (
int i = 0; i < translatedLanguageNames.Length; i++)
340 if (translatedLanguageNames[i, 0] == language)
return translatedLanguageNames[i, 1];
343 DebugConsole.ThrowError(
"No translated language name found for " + language);
347 private static List<string> ConvertConversationsToXML(
string[] csvContent,
string language)
349 List<string> xmlContent =
new List<string>
354 string translatedName = GetTranslatedName(language);
355 bool nowhitespace = TextManager.IsCJK(translatedName);
357 int languageColumn = -1;
358 string[] headerSplit = csvContent[0].Split(separator);
359 for (
int i = 0; i < headerSplit.Length; i++)
361 if (headerSplit[i] == language ||
362 (language ==
"English" && headerSplit[i]==
"Line (Original)"))
369 xmlContent.Add($
"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\" nowhitespace=\"{nowhitespace}\">");
371 conversationClosingIndent.Clear();
372 int conversationStart = 1;
374 xmlContent.Add(
string.Empty);
376 for (
int i = conversationStart; i < csvContent.Length; i++)
378 string[] split = csvContent[i].Split(separator);
380 for (
int j = 0; j < split.Length; j++)
382 if (split[j] ==
string.Empty) { emptyFields++; }
384 if (emptyFields == split.Length)
386 HandleClosingElements(xmlContent, 0);
387 xmlContent.Add(
string.Empty);
390 else if (emptyFields == split.Length - 1 && split[0] !=
string.Empty)
392 xmlContent.Add($
"<!-- {split[0]} -->");
396 string line = split[languageColumn].Replace(
"\"",
"");
397 string speaker = split[2];
398 int depthIndex =
int.Parse(split[3]);
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(
",",
".");
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)}";
416 bool nextIsSubConvo =
false;
419 if (i < csvContent.Length - 1)
421 string[] nextConversationElement = csvContent[i + 1].Split(separator);
423 if (nextConversationElement[3] !=
string.Empty)
425 nextDepth =
int.Parse(nextConversationElement[3]);
426 nextIsSubConvo = nextDepth > depthIndex;
431 xmlContent.Add(element.TrimEnd() +
"/>");
432 if (nextDepth < depthIndex)
434 HandleClosingElements(xmlContent, nextDepth);
439 xmlContent.Add(element.TrimEnd() +
">");
440 conversationClosingIndent.Add(depthIndex);
446 xmlContent.Add(element.TrimEnd() +
"/>");
447 for (
int j = depthIndex - 1; j >= 0; j--)
449 HandleClosingElements(xmlContent, j);
454 xmlContent.Add(
string.Empty);
455 xmlContent.Add(
"</Conversations>");
460 private static void HandleClosingElements(List<string> xmlContent,
int targetDepth)
462 if (conversationClosingIndent.Count == 0) {
return; }
464 for (
int k = conversationClosingIndent.Count - 1; k >= 0; k--)
466 int currentIndent = conversationClosingIndent[k];
467 if (currentIndent < targetDepth) {
break; }
468 xmlContent.Add($
"{GetIndenting(currentIndent)}</Conversation>");
469 conversationClosingIndent.RemoveAt(k);
473 private static string GetIndenting(
int depthIndex)
475 string indenting =
string.Empty;
477 for (
int i = 0; i < depthIndex; i++)
485 private static string GetVariable(
string name,
string value)
487 if (value ==
string.Empty)
493 return $
"{name}=\"{value}\" ";