diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs
index 13020ff..d01b108 100644
--- a/MiraAPI/Roles/ICustomRole.cs
+++ b/MiraAPI/Roles/ICustomRole.cs
@@ -22,7 +22,7 @@ public interface ICustomRole : IOptionable
///
/// Gets the id part used to build the role's translation keys. It is recommended to call it ModGuid.Role.Faction
///
- string IdPrefix => GetType().Namespace!;
+ string IdPrefix => MiraLocaleManager.BuildTranslationId(GetType().Namespace!, "Role", Team.ToString());
///
/// Gets the name of the role.
diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs
index d0a642a..90f0df4 100644
--- a/MiraAPI/Translation/MiraLocaleManager.cs
+++ b/MiraAPI/Translation/MiraLocaleManager.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Xml;
using MiraAPI.Utilities;
+using MonoMod.Utils;
using Reactor.Localization.Utilities;
using UnityEngine;
@@ -14,7 +15,9 @@ public static class MiraLocaleManager
{
private const string LangDirectory = "mira_languages";
- public static readonly Dictionary>> Locale = [];
+ // Language, Xml Name, then Value
+ public static Dictionary> Locale { get; } = [];
+ public static readonly List RegisteredModIds = [];
public static readonly Dictionary RegisteredStringNames = [];
internal static readonly Dictionary StringNamesLookup = [];
@@ -88,22 +91,19 @@ public static class MiraLocaleManager
}
///
- /// 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.
+ /// Registers translations for a mod.
/// Call once per mod during plugin Load().
///
/// The mod GUID (e.g., "mira.example").
public static void Register(string modGuid)
{
var callingAssembly = Assembly.GetCallingAssembly();
- var dir = GetModLangDir(modGuid);
- LoadInternalStrings(callingAssembly, callingAssembly.GetName().Name!, modGuid, dir);
+ LoadInternalStrings(callingAssembly, callingAssembly.GetName().Name!, modGuid);
}
///
- /// 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.
+ /// Registers translations for a mod.
/// Call once per mod during plugin Load().
///
/// The mod GUID (e.g., "mira.example").
@@ -111,80 +111,95 @@ public static class MiraLocaleManager
public static void Register(string modGuid, string internalName)
{
var callingAssembly = Assembly.GetCallingAssembly();
- var dir = GetModLangDir(modGuid);
- LoadInternalStrings(callingAssembly, internalName, modGuid, dir);
+ LoadInternalStrings(callingAssembly, internalName, modGuid);
}
///
/// Translates a key into the current language.
- /// Searches all mods, with reverse lookup fallback.
///
/// The string id to find.
/// Fallback string to use if no translation is found.
/// A based on the key provided.
public static string Get(string key, string fallback = "")
{
- var currentLang = CurrentLanguage;
+ return Get(CurrentLanguage, key, fallback != string.Empty ? fallback : key);
+ }
- foreach (var (_, modLocale) in Locale)
+ ///
+ /// Translates a key into the current language.
+ ///
+ /// The specific language to prioritize.
+ /// The string id to find.
+ /// Fallback string to use if no translation is found.
+ /// A based on the key provided.
+ public static string Get(MiraLanguage language, string key, string fallback = "")
+ {
+ if (Locale.TryGetValue(language, out var translations) &&
+ translations.TryGetValue(key, out var translation))
{
- if (modLocale.TryGetValue(currentLang, out var langDict) &&
- langDict.TryGetValue(key, out var translated))
- {
- return translated;
- }
+ return translation;
}
- foreach (var (_, modLocale) in Locale)
+ if (Locale.TryGetValue(MiraLanguage.English, out var translationsEng) &&
+ translationsEng.TryGetValue(key, out var translationEng))
{
- if (modLocale.TryGetValue(MiraLanguage.English, out var engDict) &&
- engDict.TryGetValue(key, out var english))
- {
- return english;
- }
+ return translationEng;
}
- 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;
- }
- }
- }
+ return fallback;
+ }
- if (bestStructuredKey != null) break;
+ ///
+ /// Translates a key into the current language.
+ ///
+ /// The string id to find.
+ /// List of keys to change into other text.
+ /// Fallback string to use if no translation is found.
+ /// A based on the key provided.
+ public static string GetParsed(
+ string key,
+ Dictionary parseList,
+ string fallback = "")
+ {
+ return GetParsed(CurrentLanguage, key, parseList, fallback != string.Empty ? fallback : key);
+ }
+
+ ///
+ /// Translates a key into the current language.
+ ///
+ /// The specific language to prioritize.
+ /// The string id to find.
+ /// List of keys to change into other text.
+ /// Fallback string to use if no translation is found.
+ /// A based on the key provided.
+ public static string GetParsed(
+ MiraLanguage language,
+ string key,
+ Dictionary parseList,
+ string fallback = "")
+ {
+ var text = fallback;
+
+ if (Locale.TryGetValue(MiraLanguage.English, out var translationsEng) &&
+ translationsEng.TryGetValue(key, out var translationEng))
+ {
+ text = translationEng;
}
- if (bestStructuredKey != null)
+ if (language is not MiraLanguage.English &&
+ Locale.TryGetValue(language, out var translations) &&
+ translations.TryGetValue(key, out var translation))
{
- 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;
- }
- }
+ text = translation;
}
- return fallback != string.Empty ? fallback : key;
+ foreach (var tmpText in parseList.Where(x => text.Contains(x.Key)))
+ {
+ text = text.Replace(tmpText.Key, tmpText.Value);
+ }
+
+ return text;
}
public static string BuildTranslationId(string modId, string idPart, string suffix)
@@ -215,17 +230,49 @@ public static class MiraLocaleManager
return newString;
}
- private static void LoadInternalStrings(Assembly assembly, string internalName, string modGuid, string dir)
+ public static void LoadExternalLocale()
+ {
+ CheckExternalDirectory("mira.api", BepInEx.Paths.PluginPath);
+ CheckExternalDirectory("mira.api", BepInEx.Paths.BepInExRootPath);
+ CheckExternalDirectory("mira.api", BepInEx.Paths.GameRootPath);
+ foreach (var mod in RegisteredModIds)
+ {
+ CheckExternalDirectory(mod, GetModLangDir(mod));
+ }
+ }
+
+ public static void CheckExternalDirectory(string modGuid, string dir)
{
Directory.CreateDirectory(dir);
- if (!Locale.ContainsKey(modGuid))
+ foreach (var filePath in Directory.GetFiles(dir, "*.xml"))
{
- Locale[modGuid] = [];
- }
+ 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
+ {
+ Locale.TryAdd(lang, []);
+ var xmlContent = File.ReadAllText(filePath);
+ ParseXmlFile(xmlContent, lang, false);
+ }
+ catch (Exception e)
+ {
+ Error($"Failed to load external translation {filePath}: {e.Message}");
+ }
+ }
+ }
+
+ private static void LoadInternalStrings(Assembly assembly, string internalName, string modGuid)
+ {
var resourcePrefix = $"{internalName}.Resources.Locale.";
+ RegisteredModIds.Add(modGuid);
+
var atLeastOneLoaded = false;
foreach (var locale in LangList)
{
@@ -241,9 +288,8 @@ public static class MiraLocaleManager
string xmlContent = reader.ReadToEnd();
try
{
- var dict = ParseXmlFile(xmlContent);
- Locale[modGuid][locale.Key] = dict;
- Info($"Loaded {locale.Key.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)");
+ Locale.TryAdd(locale.Key, []);
+ ParseXmlFile(xmlContent, locale.Key, true);
atLeastOneLoaded = true;
}
catch (Exception e)
@@ -256,37 +302,11 @@ public static class MiraLocaleManager
{
Error($"No internal strings were found for {internalName}!");
}
-
- 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 external {lang.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)");
- }
- catch (Exception e)
- {
- Error($"Failed to load external translation {filePath}: {e.Message}");
- }
- }
}
- private static Dictionary ParseXmlFile(string xmlContent)
+ private static void ParseXmlFile(string xmlContent, MiraLanguage language, bool loadingInternal)
{
- var dict = new Dictionary();
+ var dict = Locale[language];
XmlDocument xmlDoc = new();
try
{
@@ -295,6 +315,7 @@ public static class MiraLocaleManager
if (stringNodes != null)
{
+ var total = 0;
foreach (XmlNode node in stringNodes)
{
if (node.Attributes?["name"] != null)
@@ -316,16 +337,20 @@ public static class MiraLocaleManager
value = value.Replace("", "\n").Replace("", "&");
+ if (loadingInternal && Locale[language].ContainsKey(name))
+ {
+ Error($"String for \"{name}\" in {language} was overwritten by duplicate!");
+ }
dict[name] = value;
+ total++;
}
}
+ Info($"Loaded {language.ToDisplayString()} translation with ({total} keys)");
}
}
catch (XmlException ex)
{
Error($"XML parsing error: {ex.Message}");
}
-
- return dict;
}
}
diff --git a/MiraAPI/Translation/MiraLocalizationProvider.cs b/MiraAPI/Translation/MiraLocalizationProvider.cs
index 47cf747..3ac1133 100644
--- a/MiraAPI/Translation/MiraLocalizationProvider.cs
+++ b/MiraAPI/Translation/MiraLocalizationProvider.cs
@@ -11,6 +11,7 @@ public class MiraLocalizationProvider : LocalizationProvider
{
public override int Priority => ReactorPriority.Normal;
private static LocalizationProvider? _reactorProvider;
+ private static bool _loadedStrings;
public override bool TryGetText(StringNames stringName, out string? result)
{
@@ -39,6 +40,11 @@ public class MiraLocalizationProvider : LocalizationProvider
{
MiraApiPlugin.Culture = new(culture);
}
+ if (!_loadedStrings)
+ {
+ MiraLocaleManager.LoadExternalLocale();
+ _loadedStrings = true;
+ }
foreach (var tab in LocalSettingsTab.TabGroups)
{