This commit is contained in:
2025-12-17 15:48:23 +08:00
parent 25a9bbbfee
commit 0566b774da
49 changed files with 1157 additions and 150 deletions

View File

@@ -21,6 +21,9 @@
},
{
"path": "../../../../../../../../Users/Kalo/Downloads/openai_token-main"
},
{
"path": "../../../../../../workshop/content/294100/3622974806"
}
],
"settings": {}

View File

@@ -0,0 +1,427 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using RimWorld;
using Verse;
namespace WulaFallenEmpire.Utils
{
public static class DefInjectedExportUtility
{
private sealed class InjectionValue
{
public string Key;
public bool IsCollection;
public List<string> Values;
}
public static void ExportDefInjectedTemplateFromDefs(ModContentPack content)
{
try
{
if (content?.ModMetaData == null)
{
Messages.Message("Export failed: Mod content metadata not found.", MessageTypeDefOf.RejectInput);
return;
}
string outRoot = Path.Combine(
GenFilePaths.SaveDataFolderPath,
"WulaFallenEmpire_DefInjectedExport",
DateTime.Now.ToString("yyyyMMdd_HHmmss"));
string outDefInjected = Path.Combine(outRoot, "English", "DefInjected");
Directory.CreateDirectory(outDefInjected);
string outTsvPath = Path.Combine(outRoot, "worklist.tsv");
var entriesByFolder = new Dictionary<string, Dictionary<string, InjectionValue>>(StringComparer.OrdinalIgnoreCase);
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
List<Type> defTypes = GenTypes.AllSubclassesNonAbstract(typeof(Def)).ToList();
defTypes.Sort((a, b) => string.Compare(a.FullName, b.FullName, StringComparison.OrdinalIgnoreCase));
foreach (Type defType in defTypes)
{
DefInjectionUtility.ForEachPossibleDefInjection(
defType,
(string suggestedPath,
string normalizedPath,
bool isCollection,
string currentValue,
IEnumerable<string> currentValueCollection,
bool translationAllowed,
bool fullListTranslationAllowed,
FieldInfo fieldInfo,
Def def) =>
{
if (!translationAllowed)
{
return;
}
if (string.IsNullOrWhiteSpace(suggestedPath))
{
return;
}
if (suggestedPath.IndexOf(".modContentPack.", StringComparison.OrdinalIgnoreCase) >= 0)
{
return;
}
if (!isCollection && string.Equals(fieldInfo?.Name, "defName", StringComparison.OrdinalIgnoreCase))
{
return;
}
List<string> values;
bool collectionOut;
if (isCollection)
{
values = currentValueCollection?.Where(v => KeepValue(suggestedPath, fieldInfo, v)).ToList() ?? new List<string>();
if (values.Count == 0)
{
return;
}
collectionOut = true;
}
else
{
if (!KeepValue(suggestedPath, fieldInfo, currentValue))
{
return;
}
values = new List<string> { currentValue };
collectionOut = false;
}
if (!seenKeys.Add(suggestedPath))
{
return;
}
string folderName = GetDefInjectedFolderName(def?.GetType() ?? defType);
if (!entriesByFolder.TryGetValue(folderName, out Dictionary<string, InjectionValue> folderEntries))
{
folderEntries = new Dictionary<string, InjectionValue>(StringComparer.OrdinalIgnoreCase);
entriesByFolder.Add(folderName, folderEntries);
}
folderEntries.Add(
suggestedPath,
new InjectionValue
{
Key = suggestedPath,
IsCollection = collectionOut,
Values = values
});
},
content.ModMetaData);
}
WriteDefInjectedXmlOutputs(entriesByFolder, outDefInjected);
WriteWorklistTsv(entriesByFolder, outTsvPath);
Messages.Message($"DefInjected export written to: {outRoot}", MessageTypeDefOf.TaskCompletion);
}
catch (Exception ex)
{
Log.Error($"[WulaFallenEmpire] DefInjected export failed: {ex}");
Messages.Message("DefInjected export failed (see log).", MessageTypeDefOf.RejectInput);
}
}
private static void WriteDefInjectedXmlOutputs(
Dictionary<string, Dictionary<string, InjectionValue>> entriesByFolder,
string outDefInjectedRoot)
{
foreach (KeyValuePair<string, Dictionary<string, InjectionValue>> folderPair in entriesByFolder)
{
string folder = folderPair.Key;
Dictionary<string, InjectionValue> entries = folderPair.Value;
if (entries.Count == 0)
{
continue;
}
string safeFolder = SanitizePathSegment(folder);
string folderPath = Path.Combine(outDefInjectedRoot, safeFolder);
Directory.CreateDirectory(folderPath);
WriteXmlUtf8Indented(
BuildLanguageDataDocument(entries, todoMode: false),
Path.Combine(folderPath, "Auto_CN.xml"));
WriteXmlUtf8Indented(
BuildLanguageDataDocument(entries, todoMode: true),
Path.Combine(folderPath, "Auto_TODO.xml"));
}
}
private static XDocument BuildLanguageDataDocument(
Dictionary<string, InjectionValue> entries,
bool todoMode)
{
var languageData = new XElement("LanguageData");
foreach (InjectionValue entry in entries.Values.OrderBy(e => e.Key, StringComparer.OrdinalIgnoreCase))
{
if (entry.IsCollection)
{
var element = new XElement(entry.Key);
foreach (string value in entry.Values)
{
element.Add(new XElement("li", todoMode ? ToTodoListItem(value) : (value ?? string.Empty)));
}
languageData.Add(element);
}
else
{
string value = entry.Values.FirstOrDefault() ?? string.Empty;
languageData.Add(new XElement(entry.Key, todoMode ? "TODO" : value));
}
}
return new XDocument(new XDeclaration("1.0", "utf-8", null), languageData);
}
private static void WriteWorklistTsv(
Dictionary<string, Dictionary<string, InjectionValue>> entriesByFolder,
string outTsvPath)
{
var lines = new List<string> { "Folder\tKey\tCNSourceType\tCNSource" };
foreach (KeyValuePair<string, Dictionary<string, InjectionValue>> folderPair in entriesByFolder)
{
string folder = folderPair.Key;
foreach (InjectionValue entry in folderPair.Value.Values.OrderBy(e => e.Key, StringComparer.OrdinalIgnoreCase))
{
if (entry.IsCollection)
{
string joined = string.Join("\\n", entry.Values.Select(v => v ?? string.Empty));
lines.Add($"{EscapeTsv(folder)}\t{EscapeTsv(entry.Key)}\tlist\t{EscapeTsv(joined)}");
}
else
{
string value = entry.Values.FirstOrDefault() ?? string.Empty;
lines.Add($"{EscapeTsv(folder)}\t{EscapeTsv(entry.Key)}\ttext\t{EscapeTsv(value)}");
}
}
}
string dir = Path.GetDirectoryName(outTsvPath);
if (!string.IsNullOrEmpty(dir))
{
Directory.CreateDirectory(dir);
}
File.WriteAllLines(outTsvPath, lines, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
}
private static string GetDefInjectedFolderName(Type defType)
{
if (defType == null)
{
return "UnknownDefType";
}
string ns = defType.Namespace ?? string.Empty;
if (ns == "Verse" || ns == "RimWorld")
{
return defType.Name;
}
return defType.FullName ?? defType.Name;
}
private static string SanitizePathSegment(string segment)
{
if (string.IsNullOrEmpty(segment))
{
return "_";
}
foreach (char c in Path.GetInvalidFileNameChars())
{
segment = segment.Replace(c, '_');
}
return segment;
}
private static string ToTodoListItem(string original)
{
string s = original ?? string.Empty;
int idx = s.IndexOf("->", StringComparison.Ordinal);
if (idx >= 0)
{
return s.Substring(0, idx) + "->TODO";
}
return "TODO";
}
private static bool KeepValue(string key, FieldInfo fieldInfo, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
if (!string.IsNullOrEmpty(key))
{
if (key.IndexOf(".defName", StringComparison.OrdinalIgnoreCase) >= 0)
{
return false;
}
if (key.IndexOf(".fileName", StringComparison.OrdinalIgnoreCase) >= 0)
{
return false;
}
}
if (LooksLikeFilePath(value))
{
return false;
}
if (LooksLikeAssetPath(value))
{
return false;
}
string fieldName = fieldInfo?.Name ?? string.Empty;
if (fieldName.IndexOf("label", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
if (fieldName.IndexOf("description", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
if (fieldName.IndexOf("title", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
if (fieldName.IndexOf("text", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (c >= 0x4E00 && c <= 0x9FFF)
{
return true;
}
}
if (value.IndexOfAny(new[] { ' ', '\n', '\r', '\t' }) >= 0)
{
return true;
}
if (value.IndexOfAny(new[] { '', '。', '', '', '', '', '、', '', '', '《', '》', '“', '”', '"', '\'', ':', ';', ',', '.', '!', '?' }) >= 0)
{
return true;
}
return false;
}
private static bool LooksLikeFilePath(string value)
{
if (string.IsNullOrEmpty(value))
{
return false;
}
if (value.Length >= 3 && char.IsLetter(value[0]) && value[1] == ':' && (value[2] == '\\' || value[2] == '/'))
{
return true;
}
if (value.Contains("\\\\") || value.Contains(":\\"))
{
return true;
}
return false;
}
private static bool LooksLikeAssetPath(string value)
{
if (string.IsNullOrEmpty(value))
{
return false;
}
if (value.IndexOf('/') < 0 && value.IndexOf('\\') < 0)
{
return false;
}
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (c >= 0x4E00 && c <= 0x9FFF)
{
return false;
}
}
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (char.IsWhiteSpace(c))
{
return false;
}
}
return true;
}
private static void WriteXmlUtf8Indented(XDocument doc, string path)
{
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\n",
NewLineHandling = NewLineHandling.Replace,
OmitXmlDeclaration = false,
Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)
};
using XmlWriter writer = XmlWriter.Create(path, settings);
doc.Save(writer);
}
private static string EscapeTsv(string value)
{
if (value == null)
{
return string.Empty;
}
if (value.IndexOfAny(new[] { '\t', '\n', '\r', '"' }) >= 0)
{
return "\"" + value.Replace("\"", "\"\"") + "\"";
}
return value;
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Reflection;
using HarmonyLib;
using UnityEngine;
using Verse;
using WulaFallenEmpire.Utils;
namespace WulaFallenEmpire
{
@@ -48,6 +49,14 @@ namespace WulaFallenEmpire
listingStandard.GapLine();
listingStandard.CheckboxLabeled("Wula_EnableDebugLogs".Translate(), ref settings.enableDebugLogs, "Wula_EnableDebugLogsDesc".Translate());
listingStandard.GapLine();
listingStandard.Label("Translation tools");
Rect exportRect = listingStandard.GetRect(30f);
if (Widgets.ButtonText(exportRect, "Export DefInjected template (CN source)"))
{
DefInjectedExportUtility.ExportDefInjectedTemplateFromDefs(Content);
}
listingStandard.End();
base.DoSettingsWindowContents(inRect);
}