Add support for UI translations

This commit is contained in:
Nick J. Connors 2022-02-25 12:42:58 -05:00
parent 08c9afd988
commit d1d5b2e557
12 changed files with 200 additions and 83 deletions

View File

@ -0,0 +1,15 @@
{
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/master/NewHorizons/translation_schema.json",
"DialogueDictionary":
{
"Your ship is now equiped with a warp drive!" : "Votre fusée est maintenant équipé d'un moteur de distorsion!",
"You can use the new \"Interstellar Mode\" page in the ship log to lock-on your autopilot to another star system." : "Vous pouvez utiliser la nouvelle page \"Mode Interstellaire\" dans le journal de bord pour diriger votre pilote automatique vers un autre système solaire",
"Then just buckle up and engage the autopilot to warp there!" : "Ensuite, bouclez votre ceinture et engagez le pilote automatique pour y aller!"
},
"UIDictionary":
{
"Interstellar Mode" : "Mode Interstellaire",
"Nomai Statue" : "Statue Nomaï",
"Anti-Graviton Flux" : "Flux Anti-Gravitonique"
}
}

View File

@ -4,6 +4,7 @@ using System.Reflection;
using UnityEngine; using UnityEngine;
using NewHorizons.External.Configs; using NewHorizons.External.Configs;
using Logger = NewHorizons.Utility.Logger; using Logger = NewHorizons.Utility.Logger;
using NewHorizons.Handlers;
namespace NewHorizons.Builder.General namespace NewHorizons.Builder.General
{ {
@ -12,7 +13,7 @@ namespace NewHorizons.Builder.General
public static void Make(GameObject body, string name, IPlanetConfig config) public static void Make(GameObject body, string name, IPlanetConfig config)
{ {
MapMarker mapMarker = body.AddComponent<MapMarker>(); MapMarker mapMarker = body.AddComponent<MapMarker>();
mapMarker.SetValue("_labelID", (UITextType)Utility.AddToUITable.Add(name.ToUpper())); mapMarker.SetValue("_labelID", (UITextType)TranslationHandler.AddUI(config.Name));
var markerType = MapMarker.MarkerType.Planet; var markerType = MapMarker.MarkerType.Planet;

View File

@ -1,5 +1,6 @@
using NewHorizons.Components; using NewHorizons.Components;
using NewHorizons.External; using NewHorizons.External;
using NewHorizons.Handlers;
using NewHorizons.Utility; using NewHorizons.Utility;
using OWML.Common; using OWML.Common;
using System; using System;
@ -64,9 +65,9 @@ namespace NewHorizons.Builder.Props
SignalName.WhiteHole_GD_Receiver, SignalName.WhiteHole_GD_Receiver,
}); });
SignalFrequencyOverrides = new Dictionary<SignalFrequency, string>() { SignalFrequencyOverrides = new Dictionary<SignalFrequency, string>() {
{ SignalFrequency.Statue, "NOMAI STATUE" }, { SignalFrequency.Statue, "Nomai Statue" },
{ SignalFrequency.Default, "???" }, { SignalFrequency.Default, "???" },
{ SignalFrequency.WarpCore, "ANTI-GRAVITON FLUX" } { SignalFrequency.WarpCore, "Anti-Graviton Flux" }
}; };
_nextCustomSignalName = 200; _nextCustomSignalName = 200;
} }
@ -79,14 +80,14 @@ namespace NewHorizons.Builder.Props
if (_availableSignalNames.Count == 0) newName = (SignalName)_nextCustomSignalName++; if (_availableSignalNames.Count == 0) newName = (SignalName)_nextCustomSignalName++;
else newName = _availableSignalNames.Pop(); else newName = _availableSignalNames.Pop();
_customSignalNames.Add(newName, str.ToUpper()); _customSignalNames.Add(newName, str);
return newName; return newName;
} }
public static string GetCustomSignalName(SignalName signalName) public static string GetCustomSignalName(SignalName signalName)
{ {
_customSignalNames.TryGetValue(signalName, out string name); _customSignalNames.TryGetValue(signalName, out string name);
return name; return TranslationHandler.GetTranslation(name, TranslationHandler.TextType.UI).ToUpper();
} }
public static void Make(GameObject body, Sector sector, SignalModule module, IModBehaviour mod) public static void Make(GameObject body, Sector sector, SignalModule module, IModBehaviour mod)

View File

@ -11,6 +11,7 @@ using UnityEngine.UI;
using Logger = NewHorizons.Utility.Logger; using Logger = NewHorizons.Utility.Logger;
using NewHorizons.Builder.Handlers; using NewHorizons.Builder.Handlers;
using System; using System;
using NewHorizons.Handlers;
namespace NewHorizons.Builder.ShipLog namespace NewHorizons.Builder.ShipLog
{ {
@ -60,7 +61,7 @@ namespace NewHorizons.Builder.ShipLog
public static string GetAstroBodyShipLogName(string id) public static string GetAstroBodyShipLogName(string id)
{ {
return ShipLogHandler.GetNameFromAstroID(id) ?? id; return TranslationHandler.GetTranslation(ShipLogHandler.GetNameFromAstroID(id) ?? id, TranslationHandler.TextType.UI);
} }
private static GameObject CreateImage(GameObject nodeGO, Texture2D texture, string name, int layer) private static GameObject CreateImage(GameObject nodeGO, Texture2D texture, string name, int layer)

View File

@ -13,6 +13,7 @@ namespace NewHorizons.External.Configs
{ {
public Dictionary<string, string> DialogueDictionary; public Dictionary<string, string> DialogueDictionary;
public Dictionary<string, string> ShipLogDictionary; public Dictionary<string, string> ShipLogDictionary;
public Dictionary<string, string> UIDictionary;
public TranslationConfig(string filename) public TranslationConfig(string filename)
{ {
@ -26,6 +27,10 @@ namespace NewHorizons.External.Configs
{ {
ShipLogDictionary = (Dictionary<string, string>)(dict[nameof(ShipLogDictionary)] as Newtonsoft.Json.Linq.JObject).ToObject(typeof(Dictionary<string, string>)); ShipLogDictionary = (Dictionary<string, string>)(dict[nameof(ShipLogDictionary)] as Newtonsoft.Json.Linq.JObject).ToObject(typeof(Dictionary<string, string>));
} }
if (dict.ContainsKey(nameof(UIDictionary)))
{
UIDictionary = (Dictionary<string, string>)(dict[nameof(UIDictionary)] as Newtonsoft.Json.Linq.JObject).ToObject(typeof(Dictionary<string, string>));
}
} }
} }
} }

View File

@ -4,38 +4,93 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NewHorizons.Utility;
using Logger = NewHorizons.Utility.Logger; using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Handlers namespace NewHorizons.Handlers
{ {
public static class TranslationHandler public static class TranslationHandler
{ {
public static Dictionary<TextTranslation.Language, Dictionary<string, string>> ShipLogTranslationDictionary = new Dictionary<TextTranslation.Language, Dictionary<string, string>>(); private static Dictionary<TextTranslation.Language, Dictionary<string, string>> _shipLogTranslationDictionary = new Dictionary<TextTranslation.Language, Dictionary<string, string>>();
public static Dictionary<TextTranslation.Language, Dictionary<string, string>> DialogueTranslationDictionary = new Dictionary<TextTranslation.Language, Dictionary<string, string>>(); private static Dictionary<TextTranslation.Language, Dictionary<string, string>> _dialogueTranslationDictionary = new Dictionary<TextTranslation.Language, Dictionary<string, string>>();
private static Dictionary<TextTranslation.Language, Dictionary<string, string>> _uiTranslationDictionary = new Dictionary<TextTranslation.Language, Dictionary<string, string>>();
public enum TextType
{
SHIPLOG,
DIALOGUE,
UI
}
public static string GetTranslation(string text, TextType type)
{
Dictionary<TextTranslation.Language, Dictionary<string, string>> dictionary;
var language = TextTranslation.Get().m_language;
switch (type)
{
case TextType.SHIPLOG:
dictionary = _shipLogTranslationDictionary;
break;
case TextType.DIALOGUE:
dictionary = _dialogueTranslationDictionary;
break;
case TextType.UI:
dictionary = _uiTranslationDictionary;
break;
default:
return text;
}
if(dictionary.TryGetValue(language, out var table))
{
if(table.TryGetValue(text, out var translatedText))
{
return translatedText;
}
}
return text;
}
public static void RegisterTranslation(TextTranslation.Language language, TranslationConfig config) public static void RegisterTranslation(TextTranslation.Language language, TranslationConfig config)
{ {
if (config.ShipLogDictionary != null && config.ShipLogDictionary.Count() > 0) if (config.ShipLogDictionary != null && config.ShipLogDictionary.Count() > 0)
{ {
if (!ShipLogTranslationDictionary.ContainsKey(language)) ShipLogTranslationDictionary.Add(language, new Dictionary<string, string>()); if (!_shipLogTranslationDictionary.ContainsKey(language)) _shipLogTranslationDictionary.Add(language, new Dictionary<string, string>());
foreach (var originalKey in config.ShipLogDictionary.Keys) foreach (var originalKey in config.ShipLogDictionary.Keys)
{ {
var key = originalKey.Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", ""); var key = originalKey.Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", "");
if (!ShipLogTranslationDictionary[language].ContainsKey(key)) ShipLogTranslationDictionary[language].Add(key, config.ShipLogDictionary[originalKey]); if (!_shipLogTranslationDictionary[language].ContainsKey(key)) _shipLogTranslationDictionary[language].Add(key, config.ShipLogDictionary[originalKey]);
else Logger.LogError($"Duplicate ship log translation for {originalKey}"); else _shipLogTranslationDictionary[language][key] = config.ShipLogDictionary[originalKey];
} }
} }
if (config.DialogueDictionary != null && config.DialogueDictionary.Count() > 0) if (config.DialogueDictionary != null && config.DialogueDictionary.Count() > 0)
{ {
if (!DialogueTranslationDictionary.ContainsKey(language)) DialogueTranslationDictionary.Add(language, new Dictionary<string, string>()); if (!_dialogueTranslationDictionary.ContainsKey(language)) _dialogueTranslationDictionary.Add(language, new Dictionary<string, string>());
foreach (var originalKey in config.DialogueDictionary.Keys) foreach (var originalKey in config.DialogueDictionary.Keys)
{ {
var key = originalKey.Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", ""); var key = originalKey.Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", "");
if (!DialogueTranslationDictionary[language].ContainsKey(key)) DialogueTranslationDictionary[language].Add(key, config.DialogueDictionary[originalKey]); if (!_dialogueTranslationDictionary[language].ContainsKey(key)) _dialogueTranslationDictionary[language].Add(key, config.DialogueDictionary[originalKey]);
else Logger.LogError($"Duplicate dialogue translation for {originalKey}"); else _dialogueTranslationDictionary[language][key] = config.DialogueDictionary[originalKey];
}
}
if (config.UIDictionary != null && config.UIDictionary.Count() > 0)
{
if (!_uiTranslationDictionary.ContainsKey(language)) _uiTranslationDictionary.Add(language, new Dictionary<string, string>());
foreach (var originalKey in config.UIDictionary.Keys)
{
var key = originalKey.Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", "");
if (!_uiTranslationDictionary[language].ContainsKey(key)) _uiTranslationDictionary[language].Add(key, config.UIDictionary[originalKey]);
else _uiTranslationDictionary[language][key] = config.UIDictionary[originalKey];
//Also add an upper case version
if (!_uiTranslationDictionary[language].ContainsKey(key.ToUpper())) _uiTranslationDictionary[language].Add(key.ToUpper(), config.UIDictionary[originalKey].ToUpper());
else _uiTranslationDictionary[language][key] = config.UIDictionary[originalKey].ToUpper();
} }
} }
} }
@ -46,7 +101,7 @@ namespace NewHorizons.Handlers
var language = TextTranslation.Get().m_language; var language = TextTranslation.Get().m_language;
string text = rawText; string text = rawText;
if (DialogueTranslationDictionary.TryGetValue(language, out var dict) && dict.TryGetValue(rawText, out var translatedText)) text = translatedText; if (_dialogueTranslationDictionary.TryGetValue(language, out var dict) && dict.TryGetValue(rawText, out var translatedText)) text = translatedText;
TextTranslation.Get().m_table.Insert(key, text); TextTranslation.Get().m_table.Insert(key, text);
} }
@ -57,9 +112,32 @@ namespace NewHorizons.Handlers
var language = TextTranslation.Get().m_language; var language = TextTranslation.Get().m_language;
string text = rawText; string text = rawText;
if (ShipLogTranslationDictionary.TryGetValue(language, out var dict) && dict.TryGetValue(rawText, out var translatedText)) text = translatedText; if (_shipLogTranslationDictionary.TryGetValue(language, out var dict) && dict.TryGetValue(rawText, out var translatedText)) text = translatedText;
TextTranslation.Get().m_table.InsertShipLog(key, text); TextTranslation.Get().m_table.InsertShipLog(key, text);
} }
public static int AddUI(string rawText)
{
var uiTable = TextTranslation.Get().m_table.theUITable;
var language = TextTranslation.Get().m_language;
string text = rawText;
if (_shipLogTranslationDictionary.TryGetValue(language, out var dict) && dict.TryGetValue(rawText, out var translatedText)) text = translatedText;
text = text.ToUpper();
var key = uiTable.Keys.Max() + 1;
try
{
// Ensure it doesn't already contain our UI entry
KeyValuePair<int, string> pair = uiTable.First(x => x.Value.Equals(text));
if (pair.Equals(default(KeyValuePair<int, string>))) key = pair.Key;
}
catch (Exception) { }
TextTranslation.Get().m_table.Insert_UI(key, text);
return key;
}
} }
} }

View File

@ -102,6 +102,7 @@ namespace NewHorizons
Tools.WarpDrivePatches.Apply(); Tools.WarpDrivePatches.Apply();
Tools.OWCameraFix.Apply(); Tools.OWCameraFix.Apply();
Tools.ShipLogPatches.Apply(); Tools.ShipLogPatches.Apply();
Tools.TranslationPatches.Apply();
Logger.Log("Begin load of config files...", Logger.LogType.Log); Logger.Log("Begin load of config files...", Logger.LogType.Log);
@ -122,7 +123,7 @@ namespace NewHorizons
#region Reloading #region Reloading
private void InitializePauseMenu() private void InitializePauseMenu()
{ {
_reloadButton = ModHelper.Menus.PauseMenu.OptionsButton.Duplicate("RELOAD CONFIGS"); _reloadButton = ModHelper.Menus.PauseMenu.OptionsButton.Duplicate(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpper());
_reloadButton.OnClick += ReloadConfigs; _reloadButton.OnClick += ReloadConfigs;
UpdateReloadButton(); UpdateReloadButton();
} }
@ -218,6 +219,7 @@ namespace NewHorizons
LoadBody(LoadConfig(this, "AssetBundle/WarpDriveConfig.json")); LoadBody(LoadConfig(this, "AssetBundle/WarpDriveConfig.json"));
} }
LoadTranslations(ModHelper.Manifest.ModFolderPath + "AssetBundle/", this);
Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => AstroObjectLocator.GetAstroObject("MapSatellite").gameObject.AddComponent<MapSatelliteOrbitFix>()); Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => AstroObjectLocator.GetAstroObject("MapSatellite").gameObject.AddComponent<MapSatelliteOrbitFix>());
@ -449,9 +451,15 @@ namespace NewHorizons
} }
} }
if(Directory.Exists(folder + @"translations\")) if(Directory.Exists(folder + @"translations\"))
{
LoadTranslations(folder, mod);
}
}
private void LoadTranslations(string folder, IModBehaviour mod)
{ {
var foundFile = false; var foundFile = false;
foreach(TextTranslation.Language language in Enum.GetValues(typeof(TextTranslation.Language))) foreach (TextTranslation.Language language in Enum.GetValues(typeof(TextTranslation.Language)))
{ {
if (language == TextTranslation.Language.UNKNOWN || language == TextTranslation.Language.TOTAL) continue; if (language == TextTranslation.Language.UNKNOWN || language == TextTranslation.Language.TOTAL) continue;
@ -475,7 +483,6 @@ namespace NewHorizons
} }
if (!foundFile) Logger.LogWarning($"{mod.ModHelper.Manifest.Name} has a folder for translations but none were loaded"); if (!foundFile) Logger.LogWarning($"{mod.ModHelper.Manifest.Name} has a folder for translations but none were loaded");
} }
}
public NewHorizonsBody LoadConfig(IModBehaviour mod, string relativeDirectory) public NewHorizonsBody LoadConfig(IModBehaviour mod, string relativeDirectory)
{ {

View File

@ -27,7 +27,6 @@ namespace NewHorizons.Tools
public static void Apply() public static void Apply()
{ {
// Prefixes // Prefixes
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<ReferenceFrame>("GetHUDDisplayName", typeof(Patches), nameof(Patches.GetHUDDisplayName));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<PlayerState>("CheckShipOutsideSolarSystem", typeof(Patches), nameof(Patches.CheckShipOutersideSolarSystem)); Main.Instance.ModHelper.HarmonyHelper.AddPrefix<PlayerState>("CheckShipOutsideSolarSystem", typeof(Patches), nameof(Patches.CheckShipOutersideSolarSystem));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<SunLightParamUpdater>("LateUpdate", typeof(Patches), nameof(Patches.OnSunLightParamUpdaterLateUpdate)); Main.Instance.ModHelper.HarmonyHelper.AddPrefix<SunLightParamUpdater>("LateUpdate", typeof(Patches), nameof(Patches.OnSunLightParamUpdaterLateUpdate));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<SunSurfaceAudioController>("Update", typeof(Patches), nameof(Patches.OnSunSurfaceAudioControllerUpdate)); Main.Instance.ModHelper.HarmonyHelper.AddPrefix<SunSurfaceAudioController>("Update", typeof(Patches), nameof(Patches.OnSunSurfaceAudioControllerUpdate));
@ -66,17 +65,6 @@ namespace NewHorizons.Tools
Main.Instance.ModHelper.HarmonyHelper.AddPostfix<MapController>("OnTargetReferenceFrame", typeof(Patches), nameof(Patches.OnMapControllerOnTargetReferenceFrame)); Main.Instance.ModHelper.HarmonyHelper.AddPostfix<MapController>("OnTargetReferenceFrame", typeof(Patches), nameof(Patches.OnMapControllerOnTargetReferenceFrame));
} }
public static bool GetHUDDisplayName(ReferenceFrame __instance, ref string __result)
{
var ao = __instance.GetAstroObject();
if (ao != null && ao.GetAstroObjectName() == AstroObject.Name.CustomString)
{
__result = ao.GetCustomName();
return false;
}
return true;
}
public static bool CheckShipOutersideSolarSystem(PlayerState __instance, ref bool __result) public static bool CheckShipOutersideSolarSystem(PlayerState __instance, ref bool __result)
{ {
if (PlayerState._inBrambleDimension) return false; if (PlayerState._inBrambleDimension) return false;
@ -222,7 +210,7 @@ namespace NewHorizons.Tools
SignalBuilder.SignalFrequencyOverrides.TryGetValue(__0, out string customName); SignalBuilder.SignalFrequencyOverrides.TryGetValue(__0, out string customName);
if (customName != null) if (customName != null)
{ {
if (NewHorizonsData.KnowsFrequency(customName)) __result = customName; if (NewHorizonsData.KnowsFrequency(customName)) __result = TranslationHandler.GetTranslation(customName, TranslationHandler.TextType.UI).ToUpper();
else __result = UITextLibrary.GetString(UITextType.SignalFreqUnidentified); else __result = UITextLibrary.GetString(UITextType.SignalFreqUnidentified);
return false; return false;
} }

View File

@ -0,0 +1,46 @@
using NewHorizons.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Tools
{
public static class TranslationPatches
{
public static void Apply()
{
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<ReferenceFrame>(nameof(ReferenceFrame.GetHUDDisplayName), typeof(TranslationPatches), nameof(TranslationPatches.GetHUDDisplayName));
var canvasMapMarkerInit = typeof(CanvasMapMarker).GetMethod(nameof(CanvasMapMarker.Init), new Type[] { typeof(Canvas), typeof(Transform), typeof(string) });
Main.Instance.ModHelper.HarmonyHelper.AddPostfix(canvasMapMarkerInit, typeof(TranslationPatches), nameof(TranslationPatches.OnCanvasMapMarkerInit));
Main.Instance.ModHelper.HarmonyHelper.AddPostfix<CanvasMapMarker>(nameof(CanvasMapMarker.SetLabel), typeof(TranslationPatches), nameof(TranslationPatches.OnCanvasMapMarkerSetLabel));
}
public static bool GetHUDDisplayName(ReferenceFrame __instance, ref string __result)
{
var ao = __instance.GetAstroObject();
if (ao != null && ao.GetAstroObjectName() == AstroObject.Name.CustomString)
{
__result = TranslationHandler.GetTranslation(ao.GetCustomName(), TranslationHandler.TextType.UI);
return false;
}
return true;
}
public static void OnCanvasMapMarkerInit(CanvasMapMarker __instance)
{
__instance._label = TranslationHandler.GetTranslation(__instance._label, TranslationHandler.TextType.UI);
}
public static void OnCanvasMapMarkerSetLabel(CanvasMapMarker __instance)
{
__instance._label = TranslationHandler.GetTranslation(__instance._label, TranslationHandler.TextType.UI);
}
}
}

View File

@ -22,7 +22,7 @@ namespace NewHorizons.Tools
{ {
if (!Main.HasWarpDrive) return; if (!Main.HasWarpDrive) return;
var newPrompt = "Interstellar Mode"; var newPrompt = TranslationHandler.GetTranslation("Interstellar Mode", TranslationHandler.TextType.UI);
__instance._detectiveModePrompt.SetText(newPrompt); __instance._detectiveModePrompt.SetText(newPrompt);
var text = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/ScreenPromptListScaleRoot/ScreenPromptList_UpperRight/ScreenPrompt/Text").GetComponent<UnityEngine.UI.Text>(); var text = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/ScreenPromptListScaleRoot/ScreenPromptList_UpperRight/ScreenPrompt/Text").GetComponent<UnityEngine.UI.Text>();
text.text = newPrompt; text.text = newPrompt;

View File

@ -1,25 +0,0 @@
using OWML.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace NewHorizons.Utility
{
static class AddToUITable
{
public static int Add(string text)
{
TextTranslation.TranslationTable instance = GameObject.FindObjectOfType<TextTranslation>().GetValue<TextTranslation.TranslationTable>("m_table");
try
{
KeyValuePair<int, string> pair = instance.theUITable.First(x => x.Value.Equals(text));
if (pair.Equals(default(KeyValuePair<int, string>))) return pair.Key;
}
catch (Exception) { }
instance.Insert_UI(instance.theUITable.Keys.Max() + 1, text);
return instance.theUITable.Keys.Max();
}
}
}

View File

@ -13,7 +13,7 @@
"ShipLogDictionary": { "ShipLogDictionary": {
"$ref": "#/$defs/table" "$ref": "#/$defs/table"
}, },
"UITable": { "UIDictionary": {
"$ref": "#/$defs/table" "$ref": "#/$defs/table"
}, },
"DialogueDictionary": { "DialogueDictionary": {