mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
Adjust system for Crowdin / Weblate support
This commit is contained in:
1 parent
6a7f58effa
commit
6da1048d09
10 files changed
+366
-225
No files matched your search
@@ -1,5 +1,6 @@
|
||||
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
@@ -8,6 +9,7 @@ using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.VanillaEvents;
|
||||
using Reactor;
|
||||
using Reactor.Localization;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Utilities;
|
||||
@@ -30,6 +32,11 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
|
||||
return Config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified Culture for string manipulations.
|
||||
/// </summary>
|
||||
public static CultureInfo Culture { get; internal set; } = new("en-US");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string OptionsTitleText => "MiraAPI";
|
||||
|
||||
@@ -70,7 +77,8 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
|
||||
PluginManager = new MiraPluginManager();
|
||||
PluginManager.Initialize(this, this);
|
||||
|
||||
TranslationManager.Register("mira.api");
|
||||
LocalizationManager.Register(new MiraLocalizationProvider());
|
||||
MiraLocaleManager.Register("mira.api");
|
||||
|
||||
IL2CPPChainloader.Instance.Finished +=
|
||||
ModCompatibility
|
||||
|
||||
@@ -29,4 +29,10 @@ Outlive the timer or eliminate the crew.</string>
|
||||
<string name="gamesetting.preset.save">Save</string>
|
||||
<string name="gamesetting.roles">Roles</string>
|
||||
<string name="gamesetting.modifiers">Modifiers</string>
|
||||
<string name="roleTeam.crewmate.long">Crewmate</string>
|
||||
<string name="roleTeam.crewmate.short">Crew</string>
|
||||
<string name="roleTeam.impostor.long">Impostor</string>
|
||||
<string name="roleTeam.impostor.short">Imp</string>
|
||||
<string name="roleTeam.neutral.long">Neutral</string>
|
||||
<string name="roleTeam.neutral.short">Neut</string>
|
||||
</resources>
|
||||
File renamed without changes.
@@ -22,12 +22,12 @@ public interface ICustomRole : IOptionable
|
||||
/// <summary>
|
||||
/// Gets the name of the role.
|
||||
/// </summary>
|
||||
string RoleName => TranslationManager.BuildTranslationId(IdPart, "name");
|
||||
string RoleName => MiraLocaleManager.BuildTranslationId(IdPart, "name");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the role. Used in the Intro Cutscene.
|
||||
/// </summary>
|
||||
string RoleDescription => TranslationManager.BuildTranslationId(IdPart, "description");
|
||||
string RoleDescription => MiraLocaleManager.BuildTranslationId(IdPart, "description");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the medium description of the role. Used in the role guide.
|
||||
@@ -37,7 +37,7 @@ public interface ICustomRole : IOptionable
|
||||
/// <summary>
|
||||
/// Gets the long description of the role. Used in the Role Tab and Role Options.
|
||||
/// </summary>
|
||||
string RoleLongDescription => TranslationManager.BuildTranslationId(IdPart, "description");
|
||||
string RoleLongDescription => MiraLocaleManager.BuildTranslationId(IdPart, "description");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the faction / alignment of the role. Used in the role guide.
|
||||
|
||||
@@ -7,18 +7,24 @@ namespace MiraAPI.Translation;
|
||||
public enum MiraLanguage
|
||||
{
|
||||
English,
|
||||
Spanish,
|
||||
Latam,
|
||||
Brazilian,
|
||||
Portuguese,
|
||||
Korean,
|
||||
Russian,
|
||||
Portuguese,
|
||||
Arabic,
|
||||
Dutch,
|
||||
Filipino,
|
||||
Polish,
|
||||
SChinese,
|
||||
TChinese,
|
||||
French,
|
||||
German,
|
||||
Japanese,
|
||||
Dutch,
|
||||
Italian,
|
||||
Japanese,
|
||||
Spanish,
|
||||
SChinese,
|
||||
TChinese,
|
||||
Irish,
|
||||
Polish,
|
||||
Turkish,
|
||||
Swedish,
|
||||
Lithuanian,
|
||||
Czech,
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Translation;
|
||||
|
||||
public static class MiraLocaleManager
|
||||
{
|
||||
private const string LangDirectory = "mira_languages";
|
||||
|
||||
private static readonly Dictionary<string, Dictionary<MiraLanguage, Dictionary<string, string>>> Locale = [];
|
||||
|
||||
public static Dictionary<MiraLanguage, string> LangList { get; } = new()
|
||||
{
|
||||
{ MiraLanguage.English, "en_US" },
|
||||
{ MiraLanguage.Latam, "es_419" },
|
||||
{ MiraLanguage.Brazilian, "pt_BR" },
|
||||
{ MiraLanguage.Portuguese, "pt_PT" },
|
||||
{ MiraLanguage.Korean, "ko_KR" },
|
||||
{ MiraLanguage.Russian, "ru_RU" },
|
||||
{ MiraLanguage.Dutch, "nl_NL" },
|
||||
{ MiraLanguage.Filipino, "fil_PH" },
|
||||
{ MiraLanguage.French, "fr_FR" },
|
||||
{ MiraLanguage.German, "de_DE" },
|
||||
{ MiraLanguage.Italian, "it_IT" },
|
||||
{ MiraLanguage.Japanese, "ja_JP" },
|
||||
{ MiraLanguage.Spanish, "es_ES" },
|
||||
{ MiraLanguage.SChinese, "zh_CN" },
|
||||
{ MiraLanguage.TChinese, "zh_TW" },
|
||||
{ MiraLanguage.Irish, "ga_IE" },
|
||||
{ MiraLanguage.Polish, "pl_PL" }, // Custom
|
||||
{ MiraLanguage.Turkish, "tr_TR" }, // Custom
|
||||
{ MiraLanguage.Swedish, "sv_SE" }, // Custom
|
||||
{ MiraLanguage.Lithuanian, "lt_LT" }, // Custom
|
||||
{ MiraLanguage.Czech, "cs_CZ" }, // Custom
|
||||
};
|
||||
public static Dictionary<MiraLanguage, string> LangCultureList { get; } = new()
|
||||
{
|
||||
{ MiraLanguage.English, "en-US" },
|
||||
{ MiraLanguage.Latam, "es-419" },
|
||||
{ MiraLanguage.Brazilian, "pt-BR" },
|
||||
{ MiraLanguage.Portuguese, "pt-PT" },
|
||||
{ MiraLanguage.Korean, "ko-KR" },
|
||||
{ MiraLanguage.Russian, "ru-RU" },
|
||||
{ MiraLanguage.Dutch, "nl-NL" },
|
||||
{ MiraLanguage.Filipino, "fil-PH" },
|
||||
{ MiraLanguage.French, "fr-FR" },
|
||||
{ MiraLanguage.German, "de-DE" },
|
||||
{ MiraLanguage.Italian, "it-IT" },
|
||||
{ MiraLanguage.Japanese, "ja-JP" },
|
||||
{ MiraLanguage.Spanish, "es-ES" },
|
||||
{ MiraLanguage.SChinese, "zh-CN" },
|
||||
{ MiraLanguage.TChinese, "zh-TW" },
|
||||
{ MiraLanguage.Irish, "ga-IE" },
|
||||
{ MiraLanguage.Polish, "pl-PL" }, // Custom
|
||||
{ MiraLanguage.Turkish, "tr-TR" }, // Custom
|
||||
{ MiraLanguage.Swedish, "sv-SE" }, // Custom
|
||||
{ MiraLanguage.Lithuanian, "lt-LT" }, // Custom
|
||||
{ MiraLanguage.Czech, "cs-CZ" }, // Custom
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current language from Among Us settings.
|
||||
/// </summary>
|
||||
public static MiraLanguage CurrentLanguage
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var langName = AmongUs.Data.DataManager.Settings.Language.CurrentLanguage;
|
||||
return (MiraLanguage)langName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return MiraLanguage.English;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers translations for a mod. Copies embedded XML to mira_languages/{modGuid}/ on first run,
|
||||
/// then loads all XML files from that directory into memory.
|
||||
/// Call once per mod during plugin Load().
|
||||
/// </summary>
|
||||
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
|
||||
public static void Register(string modGuid)
|
||||
{
|
||||
var callingAssembly = Assembly.GetCallingAssembly();
|
||||
var dir = GetModLangDir(modGuid);
|
||||
|
||||
LoadInternalStrings(callingAssembly, modGuid, dir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a key into the current language.
|
||||
/// Searches all mods, with reverse lookup fallback.
|
||||
/// </summary>
|
||||
/// <param name="key">The string id to find.</param>
|
||||
/// <param name="fallback">Fallback string to use if no translation is found.</param>
|
||||
/// <returns>A <see cref="string"/> based on the key provided.</returns>
|
||||
public static string Get(string key, string fallback = "")
|
||||
{
|
||||
var currentLang = CurrentLanguage;
|
||||
|
||||
foreach (var (_, modLocale) in Locale)
|
||||
{
|
||||
if (modLocale.TryGetValue(currentLang, out var langDict) &&
|
||||
langDict.TryGetValue(key, out var translated))
|
||||
{
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (_, modLocale) in Locale)
|
||||
{
|
||||
if (modLocale.TryGetValue(MiraLanguage.English, out var engDict) &&
|
||||
engDict.TryGetValue(key, out var english))
|
||||
{
|
||||
return english;
|
||||
}
|
||||
}
|
||||
|
||||
string? bestStructuredKey = null;
|
||||
foreach (var (_, modLocale) in Locale)
|
||||
{
|
||||
if (modLocale.TryGetValue(MiraLanguage.English, out var engDict))
|
||||
{
|
||||
foreach (var (structuredKey, englishValue) in engDict)
|
||||
{
|
||||
if (englishValue == key)
|
||||
{
|
||||
bestStructuredKey = structuredKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestStructuredKey != null) break;
|
||||
}
|
||||
|
||||
if (bestStructuredKey != null)
|
||||
{
|
||||
foreach (var (_, modLocale) in Locale)
|
||||
{
|
||||
if (modLocale.TryGetValue(currentLang, out var langDict) &&
|
||||
langDict.TryGetValue(bestStructuredKey, out var revTranslated))
|
||||
{
|
||||
return revTranslated;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (_, modLocale) in Locale)
|
||||
{
|
||||
if (modLocale.TryGetValue(MiraLanguage.English, out var engDict) &&
|
||||
engDict.TryGetValue(bestStructuredKey, out var revEnglish))
|
||||
{
|
||||
return revEnglish;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fallback != string.Empty ? fallback : key;
|
||||
}
|
||||
|
||||
public static string BuildTranslationId(string idPart, string suffix)
|
||||
{
|
||||
return idPart.StartsWith('#') ? idPart[1..] : $"{idPart}.{suffix}";
|
||||
}
|
||||
|
||||
private static string GetModLangDir(string modGuid)
|
||||
{
|
||||
return Path.Combine(Application.persistentDataPath, LangDirectory, modGuid);
|
||||
}
|
||||
|
||||
private static void LoadInternalStrings(Assembly assembly, string modGuid, string dir)
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
var resourcePrefix = $"{assembly.GetName().Name}.Resources.Locale.";
|
||||
|
||||
foreach (var locale in LangList)
|
||||
{
|
||||
using var resourceStream =
|
||||
assembly.GetManifestResourceStream(resourcePrefix + locale.Value);
|
||||
if (resourceStream == null)
|
||||
{
|
||||
// Silently skipped
|
||||
continue;
|
||||
}
|
||||
|
||||
using StreamReader reader = new(resourceStream);
|
||||
string xmlContent = reader.ReadToEnd();
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlFile(xmlContent);
|
||||
Locale[modGuid][locale.Key] = dict;
|
||||
Info($"Loaded {locale.Key} translation for mod {modGuid} ({dict.Count} keys)");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to load translation {resourcePrefix}.{locale.Value}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Locale.TryGetValue(modGuid, out _))
|
||||
{
|
||||
Locale[modGuid] = [];
|
||||
}
|
||||
|
||||
foreach (var filePath in Directory.GetFiles(dir, "*"))
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
var lang = LangList.ContainsValue(fileName)
|
||||
? LangList.First(x => x.Value == fileName).Key
|
||||
: (MiraLanguage)(-1);
|
||||
if ((int)lang == -1) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlFile(filePath);
|
||||
Locale[modGuid][lang] = dict;
|
||||
Info($"Loaded {lang} translation for mod {modGuid} ({dict.Count} keys)");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to load translation {filePath}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ParseXmlFile(string filePath)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
var doc = XDocument.Load(filePath);
|
||||
|
||||
if (doc.Root == null) return dict;
|
||||
|
||||
foreach (var element in doc.Root.Elements("string"))
|
||||
{
|
||||
var name = element.Attribute("name")?.Value;
|
||||
var value = element.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(name)) continue;
|
||||
|
||||
if (value.Contains('['))
|
||||
{
|
||||
value = value.Replace("[", "<");
|
||||
}
|
||||
|
||||
if (value.Contains(']'))
|
||||
{
|
||||
value = value.Replace("]", ">");
|
||||
}
|
||||
|
||||
value = value.Replace("<nl>", "\n").Replace("<and>", "&");
|
||||
|
||||
dict[name] = value;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Linq;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using Reactor.Localization;
|
||||
using Reactor.Localization.Providers;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Translation;
|
||||
|
||||
public class MiraLocalizationProvider : LocalizationProvider
|
||||
{
|
||||
public override int Priority => ReactorPriority.Normal;
|
||||
private static LocalizationProvider? _reactorProvider;
|
||||
|
||||
public override bool TryGetText(StringNames stringName, out string? result)
|
||||
{
|
||||
if ((int)stringName < 0 && _reactorProvider!.TryGetText(stringName, out var reactorText))
|
||||
{
|
||||
if (reactorText.IsNullOrWhiteSpace())
|
||||
{
|
||||
result = "STRMISS";
|
||||
return true;
|
||||
}
|
||||
var localeText = MiraLocaleManager.Get(reactorText!);
|
||||
if (!localeText.Contains("STRMISS"))
|
||||
{
|
||||
result = localeText;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetTextFormatted(StringNames stringName, Il2CppReferenceArray<Il2CppSystem.Object> parts, out string? result)
|
||||
{
|
||||
if (!TryGetText(stringName, out result)) return false;
|
||||
|
||||
result = Il2CppSystem.String.Format(result, parts);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnLanguageChanged(SupportedLangs newLanguage)
|
||||
{
|
||||
_reactorProvider ??= LocalizationManager.Providers.First(x => x is HardCodedLocalizationProvider);
|
||||
|
||||
if (MiraLocaleManager.LangCultureList.TryGetValue((MiraLanguage)newLanguage, out var culture))
|
||||
{
|
||||
MiraApiPlugin.Culture = new(culture);
|
||||
}
|
||||
/*Warning($"<?xml version='1.0' encoding='UTF-8'?>");
|
||||
Warning($"<resources>");
|
||||
foreach (var stringName in TranslationController.Instance.currentLanguage.AllStrings)
|
||||
{
|
||||
var value = stringName.Value.Replace("\n", "\\%nl\\%");
|
||||
value = value.Replace("{", "[");
|
||||
value = value.Replace("}", "]");
|
||||
Warning($"<string name=\"{stringName.Key}\">{value}</string>");
|
||||
}
|
||||
Warning($"</resources>");*/
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,6 @@ public static class TranslationExtensions
|
||||
/// </summary>
|
||||
public static string Translate(this string key)
|
||||
{
|
||||
return TranslationManager.Translate(key);
|
||||
return MiraLocaleManager.Get(key);
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Translation;
|
||||
|
||||
public static class TranslationManager
|
||||
{
|
||||
private const string LangDirectory = "mira_languages";
|
||||
|
||||
private static readonly Dictionary<string, Dictionary<MiraLanguage, Dictionary<string, string>>> Translations = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current language from Among Us settings.
|
||||
/// </summary>
|
||||
public static MiraLanguage CurrentLanguage
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var langName = AmongUs.Data.DataManager.Settings.Language.CurrentLanguage.ToString();
|
||||
return Enum.TryParse<MiraLanguage>(langName, out var result) ? result : MiraLanguage.English;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return MiraLanguage.English;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers translations for a mod. Copies embedded XML to mira_languages/{modGuid}/ on first run,
|
||||
/// then loads all XML files from that directory into memory.
|
||||
/// Call once per mod during plugin Load().
|
||||
/// </summary>
|
||||
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
|
||||
public static void Register(string modGuid)
|
||||
{
|
||||
var callingAssembly = Assembly.GetCallingAssembly();
|
||||
var dir = GetModLangDir(modGuid);
|
||||
|
||||
// Copy embedded XML resources to disk (first-run only)
|
||||
CopyEmbeddedToDisk(callingAssembly, modGuid, dir);
|
||||
|
||||
// Load all XML files from disk into memory
|
||||
LoadFromDisk(modGuid, dir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a key into the current language.
|
||||
/// Searches all mods, with reverse lookup fallback.
|
||||
/// </summary>
|
||||
public static string Translate(string key)
|
||||
{
|
||||
var currentLang = CurrentLanguage;
|
||||
|
||||
foreach (var (_, modTranslations) in Translations)
|
||||
{
|
||||
if (modTranslations.TryGetValue(currentLang, out var langDict) &&
|
||||
langDict.TryGetValue(key, out var translated))
|
||||
{
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (_, modTranslations) in Translations)
|
||||
{
|
||||
if (modTranslations.TryGetValue(MiraLanguage.English, out var engDict) &&
|
||||
engDict.TryGetValue(key, out var english))
|
||||
{
|
||||
return english;
|
||||
}
|
||||
}
|
||||
|
||||
string? bestStructuredKey = null;
|
||||
foreach (var (_, modTranslations) in Translations)
|
||||
{
|
||||
if (modTranslations.TryGetValue(MiraLanguage.English, out var engDict))
|
||||
{
|
||||
foreach (var (structuredKey, englishValue) in engDict)
|
||||
{
|
||||
if (englishValue == key)
|
||||
{
|
||||
bestStructuredKey = structuredKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestStructuredKey != null) break;
|
||||
}
|
||||
|
||||
if (bestStructuredKey != null)
|
||||
{
|
||||
foreach (var (_, modTranslations) in Translations)
|
||||
{
|
||||
if (modTranslations.TryGetValue(currentLang, out var langDict) &&
|
||||
langDict.TryGetValue(bestStructuredKey, out var revTranslated))
|
||||
{
|
||||
return revTranslated;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (_, modTranslations) in Translations)
|
||||
{
|
||||
if (modTranslations.TryGetValue(MiraLanguage.English, out var engDict) &&
|
||||
engDict.TryGetValue(bestStructuredKey, out var revEnglish))
|
||||
{
|
||||
return revEnglish;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public static string BuildTranslationId(string idPart, string suffix)
|
||||
{
|
||||
return idPart.StartsWith('#') ? idPart[1..] : $"{idPart}.{suffix}";
|
||||
}
|
||||
|
||||
private static string GetModLangDir(string modGuid)
|
||||
{
|
||||
return Path.Combine(Application.persistentDataPath, LangDirectory, modGuid);
|
||||
}
|
||||
|
||||
private static void CopyEmbeddedToDisk(Assembly assembly, string modGuid, string dir)
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var resourcePrefix = $"{assembly.GetName().Name}.Resources.Translations.";
|
||||
|
||||
foreach (var resourceName in assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (!resourceName.StartsWith(resourcePrefix)) continue;
|
||||
|
||||
var fileName = resourceName.Substring(resourcePrefix.Length);
|
||||
var diskPath = Path.Combine(dir, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null) continue;
|
||||
|
||||
using var fs = File.Create(diskPath);
|
||||
stream.CopyTo(fs);
|
||||
Info($"Installed translation: {modGuid}/{fileName}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Warning($"Failed to copy {resourceName}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadFromDisk(string modGuid, string dir)
|
||||
{
|
||||
if (!Directory.Exists(dir)) return;
|
||||
|
||||
if (!Translations.ContainsKey(modGuid))
|
||||
{
|
||||
Translations[modGuid] = [];
|
||||
}
|
||||
|
||||
foreach (var filePath in Directory.GetFiles(dir, "*.xml"))
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
if (!Enum.TryParse<MiraLanguage>(fileName, out var lang)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlFile(filePath);
|
||||
Translations[modGuid][lang] = dict;
|
||||
Info($"Loaded {lang} translation for mod {modGuid} ({dict.Count} keys)");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to load translation {filePath}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ParseXmlFile(string filePath)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
var doc = XDocument.Load(filePath);
|
||||
|
||||
if (doc.Root == null) return dict;
|
||||
|
||||
foreach (var element in doc.Root.Elements("string"))
|
||||
{
|
||||
var name = element.Attribute("name")?.Value;
|
||||
var value = element.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(name)) continue;
|
||||
|
||||
value = value.Replace("<nl>", "\n").Replace("<and>", "&");
|
||||
|
||||
dict[name] = value;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ namespace MiraAPI.Utilities.Components;
|
||||
/// <summary>
|
||||
/// Utility component for arranging objects in a centered layout, similar to <see cref="GridArrange"/>.
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
/// <param name="iPtr">The <see cref="IntPtr"/> for the component.</param>
|
||||
[RegisterInIl2Cpp]
|
||||
public class CenteredGridArrange(IntPtr ptr) : MonoBehaviour(ptr)
|
||||
public class CenteredGridArrange(IntPtr iPtr) : MonoBehaviour(iPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the cell size, which is used for spacing.
|
||||
|
||||
Reference in new issue
Block a user