From 0b81a8808a67d693364e53f9c0fa9d12b96e7692 Mon Sep 17 00:00:00 2001 From: linmeideli <2558527373@qq.com> Date: Fri, 19 Jun 2026 23:35:37 +0800 Subject: [PATCH 01/16] Add Translation API --- .../Buttons/Freezer/FreezeButton.cs | 2 +- MiraAPI.Example/Buttons/MeetingButton.cs | 2 +- .../Buttons/NeutralKillerButton.cs | 2 +- .../Buttons/Teleporter/TeleportButton.cs | 2 +- MiraAPI.Example/Buttons/ThinkButton.cs | 8 +- MiraAPI.Example/ExampleColors.cs | 6 +- MiraAPI.Example/ExampleLocalSetings.cs | 4 +- MiraAPI.Example/ExamplePlugin.cs | 5 + .../GameOver/NeutralKillerGameOver.cs | 3 +- MiraAPI.Example/Modifiers/CaptainModifier.cs | 5 +- .../Modifiers/Freezer/FreezeModifier.cs | 2 +- .../Modifiers/HighPriorityModifier.cs | 4 +- MiraAPI.Example/Options/ExampleOptions.cs | 10 +- MiraAPI.Example/Options/ExampleOptions2.cs | 12 +- .../Modifiers/CaptainModifierSettings.cs | 8 +- .../Modifiers/FreezeModifierSettings.cs | 4 +- .../Modifiers/GeneralModifierOptions.cs | 6 +- .../Options/Roles/FreezerRoleSettings.cs | 6 +- .../Options/Roles/TeleporterOptions.cs | 8 +- .../Resources/Translations/English.xml | 66 ++++++ .../Resources/Translations/SChinese.xml | 66 ++++++ MiraAPI.Example/Roles/ChameleonRole.cs | 4 +- MiraAPI.Example/Roles/FreezerRole.cs | 4 +- MiraAPI.Example/Roles/MayorRole.cs | 4 +- MiraAPI.Example/Roles/NeutralKillerRole.cs | 10 +- MiraAPI.Example/Roles/TeleporterRole.cs | 4 +- MiraAPI/Colors/PaletteManager.cs | 6 +- MiraAPI/GameModes/CustomGameModeManager.cs | 4 +- MiraAPI/GameModes/DefaultMode.cs | 8 +- .../Attributes/ModdedEnumOptionAttribute.cs | 3 +- .../Attributes/ModdedNumberOptionAttribute.cs | 3 +- .../Attributes/ModdedToggleOptionAttribute.cs | 3 +- .../GameOptions/OptionTypes/ModdedOption.cs | 5 +- .../OptionTypes/ModdedPlayerOption.cs | 3 +- .../OptionTypes/ModdedToggleOption.cs | 3 +- MiraAPI/Hud/CustomActionButton.cs | 3 +- MiraAPI/LocalSettings/LocalSettingsButton.cs | 3 +- MiraAPI/LocalSettings/LocalSettingsTab.cs | 9 +- .../SettingTypes/LocalEnumSetting.cs | 3 +- .../SettingTypes/LocalNumberSetting.cs | 3 +- .../SettingTypes/LocalSliderSetting.cs | 5 +- .../SettingTypes/LocalToggleSetting.cs | 5 +- MiraAPI/MiraApiPlugin.cs | 3 + MiraAPI/MiraApiSettings.cs | 3 +- MiraAPI/Modifiers/ModifierComponent.cs | 12 +- .../ModifierDisplayComponent.cs | 3 +- .../ModifierDisplay/ModifierUiComponent.cs | 5 +- MiraAPI/Modifiers/ModifierManager.cs | 7 +- .../Patches/DetectiveNotesMinigamePatch.cs | 4 +- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 3 +- MiraAPI/Patches/IntroCutscenePatches.cs | 5 +- .../Patches/Options/GameOptionsMenuPatch.cs | 9 +- .../Patches/Options/GamePresetsTabPatches.cs | 5 +- .../Patches/Options/LobbyViewPanePatches.cs | 10 +- MiraAPI/Patches/Options/MenuState.cs | 23 +- MiraAPI/Patches/Options/OptionsPatches.cs | 7 +- .../Patches/Options/RoleSettingMenuPatches.cs | 14 +- MiraAPI/PluginLoading/MiraPluginManager.cs | 3 +- MiraAPI/Resources/Translations/English.xml | 28 +++ MiraAPI/Resources/Translations/SChinese.xml | 27 +++ MiraAPI/Roles/CustomRoleManager.cs | 13 +- MiraAPI/Roles/CustomRoleUtils.cs | 5 +- MiraAPI/Roles/ICustomRole.cs | 11 +- MiraAPI/Translation/MiraLanguage.cs | 24 ++ MiraAPI/Translation/TranslationExtensions.cs | 15 ++ MiraAPI/Translation/TranslationManager.cs | 205 ++++++++++++++++++ MiraAPI/Utilities/Helpers.cs | 3 +- 67 files changed, 628 insertions(+), 145 deletions(-) create mode 100644 MiraAPI.Example/Resources/Translations/English.xml create mode 100644 MiraAPI.Example/Resources/Translations/SChinese.xml create mode 100644 MiraAPI/Resources/Translations/English.xml create mode 100644 MiraAPI/Resources/Translations/SChinese.xml create mode 100644 MiraAPI/Translation/MiraLanguage.cs create mode 100644 MiraAPI/Translation/TranslationExtensions.cs create mode 100644 MiraAPI/Translation/TranslationManager.cs diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 4da744d..8d02a29 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -13,7 +13,7 @@ namespace MiraAPI.Example.Buttons.Freezer; public class FreezeButton : CustomActionButton { - public override string Name => "Freeze"; + public override string Name => "button.freeze.name"; public override float Cooldown => OptionGroupSingleton.Instance.FreezeDuration; public override bool PauseTimerInVent => true; diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs index 8ca1b82..9949ae2 100644 --- a/MiraAPI.Example/Buttons/MeetingButton.cs +++ b/MiraAPI.Example/Buttons/MeetingButton.cs @@ -9,7 +9,7 @@ namespace MiraAPI.Example.Buttons; public class MeetingButton : CustomActionButton { - public override string Name => "Call Meeting"; + public override string Name => "button.callMeeting.name"; public override float Cooldown => 15; diff --git a/MiraAPI.Example/Buttons/NeutralKillerButton.cs b/MiraAPI.Example/Buttons/NeutralKillerButton.cs index 3763577..0d40183 100644 --- a/MiraAPI.Example/Buttons/NeutralKillerButton.cs +++ b/MiraAPI.Example/Buttons/NeutralKillerButton.cs @@ -10,7 +10,7 @@ namespace MiraAPI.Example.Buttons; public class NeutralKillerButton : CustomActionButton { - public override string Name => "Win Game"; + public override string Name => "button.winGame.name"; public override float Cooldown => 0f; public override LoadableAsset Sprite => ExampleAssets.ExampleButton; public override MiraKeybind Keybind => ExampleKeybinds.NeutralWinKeybind; diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs index b59d387..681a7dc 100644 --- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs +++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs @@ -12,7 +12,7 @@ namespace MiraAPI.Example.Buttons.Teleporter; public class TeleportButton : CustomActionButton { - public override string Name => "Teleport"; + public override string Name => "button.teleport.name"; public override float Cooldown => OptionGroupSingleton.Instance.TeleportCooldown.Value; diff --git a/MiraAPI.Example/Buttons/ThinkButton.cs b/MiraAPI.Example/Buttons/ThinkButton.cs index a6f6855..8cc0c63 100644 --- a/MiraAPI.Example/Buttons/ThinkButton.cs +++ b/MiraAPI.Example/Buttons/ThinkButton.cs @@ -1,4 +1,5 @@ using MiraAPI.Hud; +using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using UnityEngine; @@ -6,7 +7,7 @@ namespace MiraAPI.Example.Buttons; public class ThinkButton : CustomActionButton { - public override string Name => "Think"; + public override string Name => "button.think.name"; public override float Cooldown => 15f; public override float EffectDuration => 10f; public override int MaxUses => 1; @@ -14,13 +15,12 @@ public class ThinkButton : CustomActionButton public override LoadableAsset Sprite => ExampleAssets.ExampleButton; protected override void OnClick() { - // This button does absolutely nothing besides changing its text - Button!.OverrideText("Thinking..."); + Button!.OverrideText("button.think.thinking".Translate()); } public override void OnEffectEnd() { - Button!.OverrideText("Think"); + Button!.OverrideText("button.think.name".Translate()); } public override bool IsEffectCancellable() diff --git a/MiraAPI.Example/ExampleColors.cs b/MiraAPI.Example/ExampleColors.cs index d61eb49..cde5aae 100644 --- a/MiraAPI.Example/ExampleColors.cs +++ b/MiraAPI.Example/ExampleColors.cs @@ -6,12 +6,12 @@ namespace MiraAPI.Example; [RegisterCustomColors] public static class ExampleColors { - public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f)) + public static CustomColor Cerulean { get; } = new("color.cerulean", new Color(0.0f, 0.48f, 0.65f)) { ColorBrightness = CustomColorBrightness.Lighter, }; - public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f)); + public static CustomColor Rose { get; } = new("color.rose", new Color(0.98f, 0.26f, 0.62f)); - public static CustomColor Gold { get; } = new("Gold", new Color(1.0f, 0.84f, 0.0f)); + public static CustomColor Gold { get; } = new("color.gold", new Color(1.0f, 0.84f, 0.0f)); } diff --git a/MiraAPI.Example/ExampleLocalSetings.cs b/MiraAPI.Example/ExampleLocalSetings.cs index bac0507..5825914 100644 --- a/MiraAPI.Example/ExampleLocalSetings.cs +++ b/MiraAPI.Example/ExampleLocalSetings.cs @@ -8,7 +8,7 @@ namespace MiraAPI.Example; public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config) { - public override string TabName => "Mira Example"; + public override string TabName => "settings.miraExample.name"; protected override bool ShouldCreateLabels => false; public override LocalSettingTabAppearance TabAppearance => new() @@ -18,7 +18,7 @@ public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config) }; [LocalSettingsButton] - public LocalSettingsButton ExampleButton { get; private set; } = new("Example Button", OnExampleButtonClick); + public LocalSettingsButton ExampleButton { get; private set; } = new("settings.miraExample.button.exampleButton", OnExampleButtonClick); [LocalToggleSetting] public ConfigEntry ExampleToggle { get; private set; } = config.Bind("General", "Example Bool", true); diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index b20ec41..3ff65d6 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -3,6 +3,7 @@ using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using HarmonyLib; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; @@ -16,6 +17,10 @@ namespace MiraAPI.Example; [ReactorModFlags(ModFlags.RequireOnAllClients)] public partial class ExamplePlugin : BasePlugin, IMiraPlugin { + public ExamplePlugin() + { + TranslationManager.Register("mira.example"); + } public Harmony Harmony { get; } = new(Id); public string OptionsTitleText => "Mira API\nExample Mod"; public ConfigFile GetConfigFile() => Config; diff --git a/MiraAPI.Example/GameOver/NeutralKillerGameOver.cs b/MiraAPI.Example/GameOver/NeutralKillerGameOver.cs index 351d593..6cd9c38 100644 --- a/MiraAPI.Example/GameOver/NeutralKillerGameOver.cs +++ b/MiraAPI.Example/GameOver/NeutralKillerGameOver.cs @@ -1,5 +1,6 @@ using MiraAPI.Example.Roles; using MiraAPI.GameEnd; +using MiraAPI.Translation; using MiraAPI.Utilities; using UnityEngine; @@ -14,7 +15,7 @@ public class NeutralKillerGameOver : CustomGameOver public override void AfterEndGameSetup(EndGameManager endGameManager) { - endGameManager.WinText.text = "Outcast Killer Wins!"; + endGameManager.WinText.text = "gameover.outcastKiller.win".Translate(); endGameManager.WinText.color = Color.magenta; endGameManager.BackgroundBar.material.SetColor(ShaderID.Color, Color.magenta); } diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 9e20b56..4cf16b5 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -2,6 +2,7 @@ using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; + using MiraAPI.Utilities.Assets; using UnityEngine; @@ -9,7 +10,7 @@ namespace MiraAPI.Example.Modifiers; public class CaptainModifier : GameModifier { - public override string ModifierName => "Captain"; + public override string ModifierName => "modifier.captain.name"; public override LoadableAsset? ModifierIcon => ExampleAssets.CallMeetingButton; public override void OnDeath(DeathReason reason) @@ -19,7 +20,7 @@ public class CaptainModifier : GameModifier public override string GetDescription() { - return $"You can call a meeting from anywhere on the map."; + return "modifier.captain.description"; } public override int GetAssignmentChance() diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs index 8eee41e..678707f 100644 --- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs +++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs @@ -7,7 +7,7 @@ namespace MiraAPI.Example.Modifiers.Freezer; public class FreezeModifier : TimedModifier { - public override string ModifierName => "Freezed"; + public override string ModifierName => "modifier.freezed.name"; public override bool HideOnUi => false; public override float Duration => 15f; public override bool AutoStart => true; diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index f2a1e03..3d7c444 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -4,11 +4,11 @@ namespace MiraAPI.Example.Modifiers; public class HighPriorityModifier : GameModifier { - public override string ModifierName => "High Priority"; + public override string ModifierName => "modifier.highPriority.name"; public override string GetDescription() { - return "You are high priority! Idrk what this means either tbh..."; + return "modifier.highPriority.description"; } public override int GetAssignmentChance() diff --git a/MiraAPI.Example/Options/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs index 53bc9eb..055bdb3 100644 --- a/MiraAPI.Example/Options/ExampleOptions.cs +++ b/MiraAPI.Example/Options/ExampleOptions.cs @@ -7,19 +7,19 @@ namespace MiraAPI.Example.Options; public class ExampleOptions : AbstractOptionGroup { - public override string GroupName => "Example Options 1"; + public override string GroupName => "options.exampleOptions1.name"; public override Color GroupColor => Color.green; - [ModdedToggleOption("Toggle Opt 1")] + [ModdedToggleOption("options.exampleOptions1.toggleOpt1")] public bool ToggleOpt { get; set; } = false; - [ModdedToggleOption("Toggle Opt 2")] + [ModdedToggleOption("options.exampleOptions1.toggleOpt1")] public bool ToggleOpt2 { get; set; } = true; - [ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)] + [ModdedNumberOption("options.exampleOptions1.numberOpt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)] public float NumberOpt { get; set; } = 4f; - [ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])] + [ModdedEnumOption("options.exampleOptions1.bestApi", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])] public BestApi Opt { get; set; } = BestApi.MiraAPI; } diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs index 0f17078..92f0c15 100644 --- a/MiraAPI.Example/Options/ExampleOptions2.cs +++ b/MiraAPI.Example/Options/ExampleOptions2.cs @@ -6,22 +6,22 @@ namespace MiraAPI.Example.Options; public class ExampleOptions2 : AbstractOptionGroup { - public override string GroupName => "Example Options 2"; + public override string GroupName => "options.exampleOptions2.name"; public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue. public override MenuCategory ParentMenu => MenuCategory.CustomTwo; - public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false); - public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever"); - public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false); + public ModdedPlayerOption PlayerOption2 { get; } = new("options.exampleOptions2.youMustChoseAPlayer", false); + public ModdedPlayerOption PlayerOption { get; } = new("options.exampleOptions2.ehWhatever"); + public ModdedToggleOption ToggleOpt1 { get; } = new("options.exampleOptions2.toggleOption1", false); - public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false) + public ModdedToggleOption ToggleOpt2 { get; } = new("options.exampleOptions2.toggleOption2", false) { Visible = () => OptionGroupSingleton.Instance.ToggleOpt1, // implicit cast from ModdedToggleOption to bool }; - public ModdedEnumOption EnumOpt { get; } = new("Enum Opt", 0) + public ModdedEnumOption EnumOpt { get; } = new("options.exampleOptions2.enumOpt", 0) { ChangedEvent = x => Logger.Info($"changed Enum Opt to {x}"), }; diff --git a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs index 0246a1d..73ef735 100644 --- a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs +++ b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs @@ -6,14 +6,14 @@ namespace MiraAPI.Example.Options.Modifiers; public class CaptainModifierSettings : AbstractOptionGroup { - public override string GroupName => "Captain Settings"; + public override string GroupName => "options.captainSettings.name"; - [ModdedNumberOption("Amount", 0, 5)] + [ModdedNumberOption("options.captainSettings.amount", 0, 5)] public float Amount { get; set; } = 1; - [ModdedNumberOption("Chance", 0, 100, 10)] + [ModdedNumberOption("options.captainSettings.chance", 0, 100, 10)] public float Chance { get; set; } = 50; - [ModdedNumberOption("Uses", 1, 5)] + [ModdedNumberOption("options.captainSettings.uses", 1, 5)] public float NumUses { get; set; } = 3; } diff --git a/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs index 13cdef9..8cd0117 100644 --- a/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs +++ b/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs @@ -6,8 +6,8 @@ namespace MiraAPI.Example.Options.Modifiers; public class FreezeModifierSettings : AbstractOptionGroup { - public override string GroupName => "Freeze"; + public override string GroupName => "options.freezeModifierSettings.name"; - [ModdedToggleOption("Use Color")] + [ModdedToggleOption("options.freezeModifierSettings.useColor")] public bool UseColor { get; set; } = true; } diff --git a/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs index 0bcbcdc..6744d5d 100644 --- a/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs +++ b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs @@ -5,15 +5,15 @@ namespace MiraAPI.Example.Options.Modifiers; public class GeneralModifierOptions : AbstractOptionGroup { - public override string GroupName => "General"; + public override string GroupName => "options.generalModifier.name"; public override uint GroupPriority => 0; public override MenuCategory ParentMenu => MenuCategory.Modifiers; - [ModdedToggleOption("Some Boolean Option")] + [ModdedToggleOption("options.generalModifier.someBooleanOption")] public bool SomeBooleanOption { get; set; } = true; - [ModdedNumberOption("Number Option", 0, 10, 1)] + [ModdedNumberOption("options.generalModifier.numberOption", 0, 10, 1)] public float NumberOption { get; set; } = 5; } diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index 3c1fdeb..f42edf5 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -13,11 +13,11 @@ public class FreezerRoleSettings : AbstractOptionGroup TmpSpriteUtils.CreateSpriteAsset( MiraAssets.ImpostorFile.LoadAsset(), "ApiExample.Role.Impostor.FreezerRole")); - public override string GroupName => "Custom Role"; + public override string GroupName => "options.freezerRoleSettings.name"; - [ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)] + [ModdedNumberOption("options.freezerRoleSettings.freezeDuration", 1, 15, 1, MiraNumberSuffixes.Seconds)] public float FreezeDuration { get; set; } = 5; - [ModdedNumberOption("Freeze Uses", 1, 5)] + [ModdedNumberOption("options.freezerRoleSettings.freezeUses", 1, 5)] public float FreezeUses { get; set; } = 1; } diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs index d05569b..2f8450e 100644 --- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs +++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs @@ -8,13 +8,13 @@ namespace MiraAPI.Example.Options.Roles; public class TeleporterOptions : AbstractRoleOptionGroup { - public override string GroupName => "Teleporter"; + public override string GroupName => "options.teleporterOptions.name"; - public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds); + public ModdedNumberOption TeleportCooldown { get; set; } = new("options.teleporterOptions.teleportCooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds); - [ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)] + [ModdedNumberOption("options.teleporterOptions.teleportDuration", 5, 25, 1, MiraNumberSuffixes.Seconds)] public float TeleportDuration { get; set; } = 10; - [ModdedNumberOption("Zoom Distance", 4, 15)] + [ModdedNumberOption("options.teleporterOptions.zoomDistance", 4, 15)] public float ZoomDistance { get; set; } = 6; } diff --git a/MiraAPI.Example/Resources/Translations/English.xml b/MiraAPI.Example/Resources/Translations/English.xml new file mode 100644 index 0000000..f6256c0 --- /dev/null +++ b/MiraAPI.Example/Resources/Translations/English.xml @@ -0,0 +1,66 @@ + + + Teleporter + Zoom out and teleport across the map! + Freezer + Freeze another player for a duration of time. + Mayor + You get extra votes! + Chamelon + Stay invisible while not moving. + Outcast Killer + Outcast who can kill. + Outcast + OUTCAST + You are an Outcast. You do not have a team. + Think + Thinking... + Freeze + Teleport + Call Meeting + Win Game + Captain + You can call a meeting from anywhere on the map. + Freezed + High Priority + You are high priority! Idrk what this means either tbh... + Example Options 1 + Toggle Opt 1 + Toggle Opt 2 + Number Opt + Best API + Example Options 2 + YOU MUST CHOSE A PLAYER + Eh whatever + Toggle Option 1 + Toggle Option 2 + Enum Opt + Captain Settings + Amount + Chance + Uses + Freeze + Use Color + General + Some Boolean Option + Number Option + Custom Role + Freeze Duration + Freeze Uses + Teleporter + Teleport Cooldown + Teleport Duration + Zoom Distance + Mira Example + Example Bool + Example Bool 2 + Slider + Example Number + Example Multplier + Example Enum + Example Button + Outcast Killer Wins! + Cerulean + Rose + Gold + diff --git a/MiraAPI.Example/Resources/Translations/SChinese.xml b/MiraAPI.Example/Resources/Translations/SChinese.xml new file mode 100644 index 0000000..68f34e9 --- /dev/null +++ b/MiraAPI.Example/Resources/Translations/SChinese.xml @@ -0,0 +1,66 @@ + + + 传送者 + 缩小视野并在地图上传送! + 冰冻者 + 将其他玩家冰冻一段时间。 + 市长 + 你拥有额外投票权! + 变色龙 + 静止不动时保持隐身。 + 流放杀手 + 可以击杀的流放者。 + 流放者 + 流放者 + 你是流放者,你没有队伍。 + 思考 + 思考中... + 冰冻 + 传送 + 召集会议 + 赢得游戏 + 队长 + 你可以在地图上任意位置召集会议。 + 已被冰冻 + 高优先级 + 你是高优先级!老实说我也不太清楚这是什么意思…… + 示例选项组 1 + 开关选项 1 + 开关选项 2 + 数字选项 + 谁是最好的API + 示例选项组 2 + 你必须选择一个玩家 + 咕咕嘎嘎 + 开关选项 1 + 开关选项 2 + 枚举选项 + 队长设置 + 数量 + 概率 + 使用次数 + 冰冻 + 使用颜色 + 通用 + 某个布尔选项 + 数字选项 + 自定义职业 + 冰冻时长 + 冰冻次数 + 传送者 + 传送冷却 + 传送持续时间 + 缩放距离 + Mira 示例插件 + 示例布尔 + 示例布尔 2 + 滑块 + 示例数字 + 示例倍数 + 示例枚举 + 示例按钮 + 流放杀手获胜! + 天蓝 + 玫瑰红 + 金色 + diff --git a/MiraAPI.Example/Roles/ChameleonRole.cs b/MiraAPI.Example/Roles/ChameleonRole.cs index f601551..bd733d7 100644 --- a/MiraAPI.Example/Roles/ChameleonRole.cs +++ b/MiraAPI.Example/Roles/ChameleonRole.cs @@ -10,8 +10,8 @@ namespace MiraAPI.Example.Roles; public class ChameloenRole : CrewmateRole, ICustomRole { - public string RoleName => "Chamelon"; - public string RoleLongDescription => "Stay invisible while not moving."; + public string RoleName => "chamelon.name"; + public string RoleLongDescription => "chamelon.description"; public string RoleDescription => RoleLongDescription; public Color RoleColor => Palette.AcceptedGreen; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs index 5a7f4bd..96adc80 100644 --- a/MiraAPI.Example/Roles/FreezerRole.cs +++ b/MiraAPI.Example/Roles/FreezerRole.cs @@ -6,8 +6,8 @@ namespace MiraAPI.Example.Roles; public class FreezerRole : ImpostorRole, ICustomRole { - public string RoleName => "Freezer"; - public string RoleLongDescription => "Freeze another player for a duration of time."; + public string RoleName => "freezer.name"; + public string RoleLongDescription => "freezer.description"; public string RoleDescription => RoleLongDescription; public Color RoleColor => Palette.Blue; public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; diff --git a/MiraAPI.Example/Roles/MayorRole.cs b/MiraAPI.Example/Roles/MayorRole.cs index 42c264f..12effc0 100644 --- a/MiraAPI.Example/Roles/MayorRole.cs +++ b/MiraAPI.Example/Roles/MayorRole.cs @@ -5,8 +5,8 @@ namespace MiraAPI.Example.Roles; public class MayorRole : CrewmateRole, ICustomRole { - public string RoleName => "Mayor"; - public string RoleLongDescription => "You get extra votes!"; + public string RoleName => "mayor.name"; + public string RoleLongDescription => "mayor.description"; public string RoleDescription => RoleLongDescription; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index 1afa4eb..695f4df 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -8,8 +8,8 @@ namespace MiraAPI.Example.Roles; public class NeutralKillerRole : ImpostorRole, ICustomRole { - public string RoleName => "Outcast Killer"; - public string RoleDescription => "Outcast who can kill."; + public string RoleName => "outcastKiller.name"; + public string RoleDescription => "outcastKiller.description"; public string RoleLongDescription => RoleDescription; public Color RoleColor => Color.magenta; public ModdedRoleTeams Team => ModdedRoleTeams.Custom; @@ -21,13 +21,13 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole CanUseVent = true, }; - public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray); + public RoleOptionsGroup RoleOptionsGroup { get; } = new("outcast.name", Color.gray); [HideFromIl2Cpp] public TeamIntroConfiguration? IntroConfiguration { get; } = new( Color.gray, - "OUTCAST", - "You are an Outcast. You do not have a team."); + "outcast.roleTitle", + "outcast.teamIntroDescription"); public override void SpawnTaskHeader(PlayerControl playerControl) { diff --git a/MiraAPI.Example/Roles/TeleporterRole.cs b/MiraAPI.Example/Roles/TeleporterRole.cs index 5b00f63..b60407b 100644 --- a/MiraAPI.Example/Roles/TeleporterRole.cs +++ b/MiraAPI.Example/Roles/TeleporterRole.cs @@ -5,8 +5,8 @@ namespace MiraAPI.Example.Roles; public class TeleporterRole : CrewmateRole, ICustomRole { - public string RoleName => "Teleporter"; - public string RoleLongDescription => "Zoom out and teleport across the map!"; + public string RoleName => "teleporter.name"; + public string RoleLongDescription => "teleporter.description"; public string RoleDescription => RoleLongDescription; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index fe7e725..c8ae990 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; +using MiraAPI.Translation; +using Reactor.Localization.Utilities; namespace MiraAPI.Colors; @@ -20,7 +22,9 @@ public static class PaletteManager { var colors = CustomColors.Select(x => x.MainColor).ToArray(); var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray(); - var stringNames = CustomColors.Select(x => x.Name).ToArray(); + var stringNames = CustomColors.Select(x => + CustomStringName.CreateAndRegister(TranslationController.Instance.GetString(x.Name).Translate()) + ).ToArray(); Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors); Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors); diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index 3edd028..57dca80 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using MiraAPI.Translation; +using Reactor.Utilities; namespace MiraAPI.GameModes; @@ -61,7 +63,7 @@ public static class CustomGameModeManager if (GameModes.Any(x => x.Key == gameMode.Id)) { - Error($"ID for gamemode {gameMode.Name} already exists!"); + Error($"ID for gamemode {gameMode.Name.Translate()} already exists!"); return; } diff --git a/MiraAPI/GameModes/DefaultMode.cs b/MiraAPI/GameModes/DefaultMode.cs index 83c11ba..f431551 100644 --- a/MiraAPI/GameModes/DefaultMode.cs +++ b/MiraAPI/GameModes/DefaultMode.cs @@ -1,8 +1,10 @@ -namespace MiraAPI.GameModes; +using MiraAPI.Translation; + +namespace MiraAPI.GameModes; public class DefaultMode : CustomGameMode { - public override string Name => "Default"; - public override string Description => "Default Among Us GameMode"; + public override string Name => "gamemode.default.name"; + public override string Description => "gamemode.default.description".Translate(); public override int Id => 0; } diff --git a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs index 5c937b6..8dc4b26 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using MiraAPI.GameOptions.OptionTypes; +using MiraAPI.Translation; namespace MiraAPI.GameOptions.Attributes; @@ -13,7 +14,7 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va { internal override IModdedOption CreateOption(object? value, PropertyInfo property) { - var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values); + var opt = new ModdedEnumOption(Title.Translate(), (int)(value ?? 0), enumType, values); return opt; } diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs index 6ff98dc..5a30db0 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using MiraAPI.GameOptions.OptionTypes; +using MiraAPI.Translation; using MiraAPI.Utilities; namespace MiraAPI.GameOptions.Attributes; @@ -22,7 +23,7 @@ public class ModdedNumberOptionAttribute( internal override IModdedOption CreateOption(object? value, PropertyInfo property) { return new ModdedNumberOption( - Title, + Title.Translate(), (float)(value ?? min + increment), min, max, diff --git a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs index 2fccf1f..838f8b6 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using MiraAPI.GameOptions.OptionTypes; +using MiraAPI.Translation; namespace MiraAPI.GameOptions.Attributes; @@ -13,7 +14,7 @@ public class ModdedToggleOptionAttribute(string title) : ModdedOptionAttribute(t { internal override IModdedOption CreateOption(object? value, PropertyInfo property) { - var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false)); + var toggleOpt = new ModdedToggleOption(Title.Translate(), (bool)(value ?? false)); return toggleOpt; } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 4300c4f..80e4363 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -2,6 +2,7 @@ using BepInEx.Configuration; using MiraAPI.Networking; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; using UnityEngine; @@ -78,10 +79,10 @@ public abstract class ModdedOption : IModdedOption protected ModdedOption(string title, T defaultValue, bool includeInPreset = true) { Id = ModdedOptionsManager.NextId; - Title = title; + Title = title.Translate(); DefaultValue = defaultValue; Value = defaultValue; - StringName = CustomStringName.CreateAndRegister(Title); + StringName = CustomStringName.CreateAndRegister(Title.Translate()); Visible = () => true; IncludeInPreset = includeInPreset; } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs index db397bd..789368f 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs @@ -2,6 +2,7 @@ using System.Linq; using Il2CppSystem.Collections.Generic; using MiraAPI.Networking; +using MiraAPI.Translation; using MiraAPI.Utilities; using UnityEngine; using Object = UnityEngine.Object; @@ -65,7 +66,7 @@ public class ModdedPlayerOption : ModdedOption playerOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; playerOption.Title = StringName; - playerOption.TitleText.text = TranslationController.Instance.GetString(StringName); + playerOption.TitleText.text = Title.Translate(); playerOption.Values = Values; playerOption.Value = Value; playerOption.playerIndex = filteredList.FindIndex(p => p.PlayerId == Value); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 55b69b9..348f853 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -1,5 +1,6 @@ using System; using MiraAPI.Networking; +using MiraAPI.Translation; using UnityEngine; using Object = UnityEngine.Object; @@ -35,7 +36,7 @@ public class ModdedToggleOption : ModdedOption toggleOption.SetUpFromData(Data, 20); toggleOption.Title = StringName; - toggleOption.TitleText.text = Title; + toggleOption.TitleText.text = Title.Translate(); toggleOption.CheckMark.enabled = Value; toggleOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 4c5df0c..d5d8956 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -5,6 +5,7 @@ using MiraAPI.Events.Mira; using MiraAPI.Keybinds; using MiraAPI.LocalSettings; using MiraAPI.Patches; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Rewired; @@ -150,7 +151,7 @@ public abstract class CustomActionButton Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent); Button.name = Name + "Button"; - Button.OverrideText(Name.ToUpperInvariant()); + Button.OverrideText(Name.Translate().ToUpperInvariant()); Button.graphic.sprite = Sprite.LoadAsset(); diff --git a/MiraAPI/LocalSettings/LocalSettingsButton.cs b/MiraAPI/LocalSettings/LocalSettingsButton.cs index 5a1f371..548488f 100644 --- a/MiraAPI/LocalSettings/LocalSettingsButton.cs +++ b/MiraAPI/LocalSettings/LocalSettingsButton.cs @@ -1,4 +1,5 @@ using System; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -51,7 +52,7 @@ public class LocalSettingsButton(string text, Action onClick) button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7); } - tmp.text = Text; + tmp.text = Text.Translate(); button.name = Text; button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); rollover.OutColor = Tab!.TabAppearance.ButtonColor; diff --git a/MiraAPI/LocalSettings/LocalSettingsTab.cs b/MiraAPI/LocalSettings/LocalSettingsTab.cs index 074d7db..7fc7e1d 100644 --- a/MiraAPI/LocalSettings/LocalSettingsTab.cs +++ b/MiraAPI/LocalSettings/LocalSettingsTab.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using BepInEx.Configuration; using HarmonyLib; using MiraAPI.Patches.LocalSettings; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -216,7 +217,7 @@ public abstract class LocalSettingsTab(ConfigFile config) tabButtonText.transform.localPosition = new Vector3(0.078f, 0, 0); tabButtonText.transform.localScale = new Vector3(0.9f, 0.9f); tabButtonText.alignment = TextAlignmentOptions.Right; - tabButtonText.text = $"{GetShortName(TabName)}"; + tabButtonText.text = $"{GetShortName(TabName.Translate())}"; var tabButton = tabButtonObject.GetComponent(); var rollover = tabButtonObject.Rollover; @@ -244,7 +245,7 @@ public abstract class LocalSettingsTab(ConfigFile config) tabButtonText.gameObject.SetActive(true); tabButtonText.transform.localPosition = new Vector3(-0.024f, 0, 0); tabButtonText.maxVisibleCharacters = int.MaxValue; - tabButtonText.text = TabName; + tabButtonText.text = TabName.Translate(); tabButtonText.alignment = TextAlignmentOptions.Left; tabButtonRend?.gameObject.SetActive(!TabAppearance.HideIconOnHover); })); @@ -266,7 +267,7 @@ public abstract class LocalSettingsTab(ConfigFile config) tabButtonText.transform.localPosition = new Vector3(0.1f, 0, 0); tabButtonText.maxVisibleCharacters = 4; - tabButtonText.text = $"{GetShortName(TabName)}"; + tabButtonText.text = $"{GetShortName(TabName.Translate())}"; })); return tabButtonObject.gameObject; @@ -280,7 +281,7 @@ public abstract class LocalSettingsTab(ConfigFile config) label.name = text; var tmp = label.GetComponent(); - tmp.text = $"{text}"; + tmp.text = $"{text.Translate()}"; tmp.alignment = TextAlignmentOptions.Center; var meshRend = label.GetComponent(); diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs index 78b0278..ac0f858 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using BepInEx.Configuration; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -127,6 +128,6 @@ public class LocalEnumSetting : LocalSettingBase /// protected override string GetValueText() { - return $"{Name}: {Values[GetValue()]}"; + return $"{Name.Translate()}: {Values[GetValue()]}"; } } diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs index fb86fdc..75d1fde 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs @@ -1,5 +1,6 @@ using System; using BepInEx.Configuration; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -140,6 +141,6 @@ public class LocalNumberSetting : LocalSettingBase { var value = GetValue(); var formated = Helpers.FormatValue(value, SuffixType, FormatString); - return $"{Name}: {formated}"; + return $"{Name.Translate()}: {formated}"; } } diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs index ae6f500..eb59cce 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs @@ -1,5 +1,6 @@ using System; using BepInEx.Configuration; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -112,9 +113,9 @@ public class LocalSliderSetting : LocalSettingBase var value = GetValue(); var formated = Helpers.FormatValue(value, SuffixType, FormatString); var maxFormated = Helpers.FormatValue(SliderRange.max, SuffixType, FormatString); - return $"{Name}: {formated} / {maxFormated}"; + return $"{Name.Translate()}: {formated} / {maxFormated}"; } - return $"{Name}"; + return $"{Name.Translate()}"; } } diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs index 54cd8ee..3a1e507 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs @@ -1,5 +1,6 @@ using System; using BepInEx.Configuration; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Localization.Utilities; using TMPro; @@ -46,9 +47,9 @@ public class LocalToggleSetting : LocalSettingBase toggleObject.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7); } - toggleObject.BaseText = CustomStringName.CreateAndRegister(Name); + toggleObject.BaseText = CustomStringName.CreateAndRegister(Name.Translate()); toggleObject.UpdateText(GetValue()); - toggleObject.name = Name; + toggleObject.name = Name.Translate(); toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor; passiveButton.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); rollover.OverColor = Tab!.TabAppearance.ToggleHoverColor; diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index 594d927..ee3375d 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -4,6 +4,7 @@ using BepInEx; using BepInEx.Unity.IL2CPP; using HarmonyLib; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; @@ -54,5 +55,7 @@ public partial class MiraApiPlugin : BasePlugin PluginManager = new MiraPluginManager(); PluginManager.Initialize(); + + TranslationManager.Register("mira.api"); } } diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index fbead71..64f3ca7 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -2,6 +2,7 @@ using MiraAPI.LocalSettings; using MiraAPI.LocalSettings.Attributes; using MiraAPI.Patches; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using UnityEngine; @@ -14,7 +15,7 @@ namespace MiraAPI; public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) { /// - public override string TabName => "Mira API"; + public override string TabName => "miraApi.name".Translate(); /// public override LocalSettingTabAppearance TabAppearance => new() diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 6432b18..da13f1a 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -8,6 +8,8 @@ using MiraAPI.LocalSettings; using MiraAPI.Modifiers.ModifierDisplay; using MiraAPI.Modifiers.Types; using MiraAPI.Patches.Roles; +using MiraAPI.Translation; +using Reactor.Utilities; using Reactor.Utilities.Attributes; using UnityEngine; @@ -76,7 +78,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } catch (Exception e) { - Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}"); + Error($"Error while deactivating modifier {modifier.ModifierName.Translate()}: {e.ToString()}"); } Modifiers.Remove(modifier); @@ -94,7 +96,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } catch (Exception e) { - Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}"); + Error($"Error while activating modifier {modifier.ModifierName.Translate()}: {e.ToString()}"); } if (modifier is TimedModifier { AutoStart: true } timer) @@ -121,7 +123,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } catch (Exception e) { - Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}"); + Error($"Error while (fixed) updating modifier {modifier.ModifierName.Translate()}: {e.ToString()}"); } } @@ -152,7 +154,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } catch (Exception e) { - Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}"); + Error($"Error while updating modifier {modifier.ModifierName.Translate()}: {e.ToString()}"); } } } @@ -351,7 +353,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) { if (!ActiveModifiers.Contains(modifier)) { - Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player."); + Error($"Cannot remove modifier {modifier.ModifierName.Translate()} because it is not active on this player."); return; } diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs index e93f477..73922f3 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -4,6 +4,7 @@ using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.Attributes; using MiraAPI.LocalSettings; +using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Attributes; using Reactor.Utilities.Extensions; @@ -153,7 +154,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) private ModifierUiComponent CreateForModifier(BaseModifier modifier) { var newMod = Instantiate(_modTemplate, _children.transform); - newMod.transform.name = modifier.ModifierName; + newMod.transform.name = modifier.ModifierName.Translate(); var comp = newMod.AddComponent(); comp.Modifier = modifier; return comp; diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs index 66ded67..861a6d0 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs @@ -1,4 +1,5 @@ using Il2CppInterop.Runtime.Attributes; +using MiraAPI.Translation; using Reactor.Utilities.Attributes; using TMPro; using UnityEngine; @@ -50,8 +51,8 @@ public class ModifierUiComponent(nint cppPtr) : MonoBehaviour(cppPtr) return; } - nameText.text = Modifier.ModifierName; - desc.text = Modifier.GetDescription(); + nameText.text = Modifier.ModifierName.Translate(); + desc.text = Modifier.GetDescription().Translate(); modBg.gameObject.SetActive(true); icon.gameObject.SetActive(Modifier.ModifierIcon != null); diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 28faa8b..ee0fb5e 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.Serialization; using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using MiraAPI.Roles; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; @@ -138,7 +139,7 @@ public static class ModifierManager { if (MiraApiPlugin.IsDevBuild) { - Warning($"No valid players for modifier {modifier.ModifierName}"); + Warning($"No valid players for modifier {modifier.ModifierName.Translate()}"); } continue; } @@ -163,7 +164,7 @@ public static class ModifierManager if (MiraApiPlugin.IsDevBuild) { Warning( - $"No available players for modifier {modifier.ModifierName} at assignment {i + 1}"); + $"No available players for modifier {modifier.ModifierName.Translate()} at assignment {i + 1}"); } break; @@ -174,7 +175,7 @@ public static class ModifierManager { if (MiraApiPlugin.IsDevBuild) { - Warning($"Valid player for modifier {modifier.ModifierName} disappeared"); + Warning($"Valid player for modifier {modifier.ModifierName.Translate()} disappeared"); } continue; diff --git a/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs b/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs index a11ec7e..5f67033 100644 --- a/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs +++ b/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs @@ -7,7 +7,7 @@ using UnityEngine; namespace MiraAPI.Patches; -[HarmonyPatch(typeof(DetectiveNotesMinigame), nameof(DetectiveNotesMinigame.SetImpostorPopup))] +/*[HarmonyPatch(typeof(DetectiveNotesMinigame), nameof(DetectiveNotesMinigame.SetImpostorPopup))] public static class DetectiveNotesMinigamePatch { public static void Prefix(DetectiveNotesMinigame __instance, bool active) @@ -39,4 +39,4 @@ public static class DetectiveNotesMinigamePatch } ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, __instance.impostorTypePopup, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false); } -} +}*/ diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index ad0cabc..f87c691 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -10,6 +10,7 @@ using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; @@ -458,7 +459,7 @@ public static class TaskAdderPatches taskAddButton.role = null; taskAddButton.MyTask = null; taskAddButton.SafePositionWorld = __instance.SafePositionWorld; - taskAddButton.Text.text = modifier.ModifierName; + taskAddButton.Text.text = modifier.ModifierName.Translate(); taskAddButton.Text.fontSizeMin = 1; taskAddButton.Text.EnableMasking(); taskAddButton.FileImage.color = modifier.FreeplayFileColor; diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 4ee990c..c44675e 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -4,6 +4,7 @@ using Il2CppInterop.Runtime.InteropTypes; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; namespace MiraAPI.Patches; @@ -78,8 +79,8 @@ public static class IntroCutscenePatches { __instance.BackgroundBar.material.SetColor(ShaderID.Color, introConfig.IntroTeamColor); __instance.TeamTitle.color = introConfig.IntroTeamColor; - __instance.TeamTitle.text = introConfig.IntroTeamTitle; - __instance.ImpostorText.text = introConfig.IntroTeamDescription; + __instance.TeamTitle.text = introConfig.IntroTeamTitle.Translate(); + __instance.ImpostorText.text = introConfig.IntroTeamDescription.Translate(); } } diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 2bc615d..aa5156e 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -6,6 +6,7 @@ using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Presets; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -257,7 +258,7 @@ internal static class GameOptionsMenuPatch Quaternion.identity, container); - categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); + categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName.Translate()), 20); categoryHeaderMasked.Background.color = group.GroupColor; categoryHeaderMasked.Divider.color = group.GroupColor; categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) @@ -276,7 +277,7 @@ internal static class GameOptionsMenuPatch categoryHeaderMasked.gameObject.SetActive(false); var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); - newText.text = "(Click to close)"; + newText.text = $"({"gamesetting.global.clicktoclose".Translate()})"; newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); newText.gameObject.GetComponent().Destroy(); @@ -293,8 +294,8 @@ internal static class GameOptionsMenuPatch { group.AllOptionsHidden = !group.AllOptionsHidden; newText.text = group.AllOptionsHidden - ? "(Click to open)" - : "(Click to close)"; + ? $"({"gamesetting.global.clicktoopen".Translate()})" + : $"({"gamesetting.global.clicktoclose".Translate()})"; })); headerBtn.SetButtonEnableState(true); diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index bc6dc3a..6824077 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -7,6 +7,7 @@ using MiraAPI.GameOptions; using MiraAPI.PluginLoading; using MiraAPI.Presets; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; @@ -45,7 +46,7 @@ internal static class GamePresetsTabPatches if (MenuState.Instance.CurrentModIdx != 0) { - __instance.PresetDescriptionText.text = "Select or create an options preset for this mod"; + __instance.PresetDescriptionText.text = "gamesetting.preset.hint".Translate(); } else { @@ -184,7 +185,7 @@ internal static class GamePresetsTabPatches // set the button text and alignment var saveText = saveButton.buttonText; - saveText.text = "Save"; + saveText.text = "gamesetting.preset.save".Translate(); saveText.GetComponent().Destroy(); saveText.alignment = TextAlignmentOptions.Center; saveText.transform.parent.localPosition = new Vector3( diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 51e2e92..dcc9b75 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -7,6 +7,7 @@ using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -45,7 +46,7 @@ public static class LobbyViewPanePatches var pos = ModifiersTabButton.transform.localPosition; pos.x = 2.1f; ModifiersTabButton.transform.localPosition = pos; - ModifiersTabButton.buttonText.text = "Modifiers"; + ModifiersTabButton.buttonText.text = "gamesetting.modifiers".Translate(); ModifiersTabButton.OnClick = new Button.ButtonClickedEvent(); ModifiersTabButton.OnClick.AddListener( (UnityAction)(() => @@ -300,7 +301,7 @@ public static class LobbyViewPanePatches true); categoryHeaderMasked.SetHeader(StringNames.Name, 61); - categoryHeaderMasked.Title.text = group.GroupName; + categoryHeaderMasked.Title.text = group.GroupName.Translate(); categoryHeaderMasked.transform.localScale = Vector3.one; categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f); menu.settingsInfo.Add(categoryHeaderMasked.gameObject); @@ -396,11 +397,12 @@ public static class LobbyViewPanePatches var group = grouping.Key; - var name = group.Name switch + var translatedName = group.Name.Translate(); + var name = translatedName switch { "Crewmate" => StringNames.CrewmateRolesHeader, "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(group.Name), + _ => CustomStringName.CreateAndRegister(translatedName), }; var categoryHeaderRoleVariant = Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); diff --git a/MiraAPI/Patches/Options/MenuState.cs b/MiraAPI/Patches/Options/MenuState.cs index d459202..6c88089 100644 --- a/MiraAPI/Patches/Options/MenuState.cs +++ b/MiraAPI/Patches/Options/MenuState.cs @@ -5,6 +5,7 @@ using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Presets; +using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Attributes; using Reactor.Utilities.Extensions; @@ -144,7 +145,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) _largeButtons[MenuButton.Modifiers] = mBtn; mBtn.name = "ModifiersButton"; mBtn.buttonText.gameObject.GetComponent().Destroy(); - mBtn.buttonText.text = "Modifiers"; + mBtn.buttonText.text = "gamesetting.modifiers".Translate(); mBtn.OnClick = new Button.ButtonClickedEvent(); mBtn.OnClick.AddListener( (UnityAction)(() => @@ -213,7 +214,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) })); var roleText = smRolesBtn.buttonText; - roleText.text = "Roles"; + roleText.text = "gamesetting.roles".Translate(); roleText.GetComponent().Destroy(); roleText.alignment = TextAlignmentOptions.Center; roleText.transform.parent.localPosition = new Vector3( @@ -238,7 +239,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) var smModBtn = Instantiate(smRolesBtn, smRolesBtn.transform.parent); _smallButtons[MenuButton.Modifiers] = smModBtn; smModBtn.name = "SmallModifiersButton"; - smModBtn.buttonText.text = "Modifiers"; + smModBtn.buttonText.text = "gamesetting.modifiers".Translate(); smModBtn.OnClick = new Button.ButtonClickedEvent(); smModBtn.OnClick.AddListener( (UnityAction)(() => @@ -383,7 +384,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) } else { - var modName = CurrentMod.MiraPlugin.OptionsTitleText; + var modName = CurrentMod.MiraPlugin.OptionsTitleText.Translate(); _text.text = $"(Page {CurrentModIdx + 1}/{ModCount + 1})\n" + modName[..Math.Min(modName.Length, 25)]; } @@ -485,12 +486,12 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) _smallButtons[MenuButton.CustomOne].gameObject.SetActive(true); _smallButtons[MenuButton.CustomOne].transform.localPosition = new Vector3(leftPos, position.y, position.z); - _smallButtons[MenuButton.CustomOne].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameOne; + _smallButtons[MenuButton.CustomOne].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameOne.Translate(); _smallButtons[MenuButton.CustomTwo].gameObject.SetActive(true); _smallButtons[MenuButton.CustomTwo].transform.localPosition = new Vector3(rightPos, position.y, position.z); - _smallButtons[MenuButton.CustomTwo].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameTwo; + _smallButtons[MenuButton.CustomTwo].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameTwo.Translate(); position.y -= 0.637f; } @@ -499,7 +500,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) { _largeButtons[MenuButton.CustomOne].gameObject.SetActive(true); _largeButtons[MenuButton.CustomOne].transform.localPosition = position; - _largeButtons[MenuButton.CustomOne].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameOne; + _largeButtons[MenuButton.CustomOne].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameOne.Translate(); position.y -= 0.637f; } else @@ -512,7 +513,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) { _largeButtons[MenuButton.CustomTwo].gameObject.SetActive(true); _largeButtons[MenuButton.CustomTwo].transform.localPosition = position; - _largeButtons[MenuButton.CustomTwo].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameTwo; + _largeButtons[MenuButton.CustomTwo].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameTwo.Translate(); position.y -= 0.637f; } else @@ -566,15 +567,15 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) case MenuCategory.Modifiers: menu.GameSettingsTab.gameObject.SetActive(true); - menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.ModifierMenuDescription; + menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.ModifierMenuDescription.Translate(); break; case MenuCategory.CustomOne: menu.GameSettingsTab.gameObject.SetActive(true); - menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.CustomOptionMenuOneDescription; + menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.CustomOptionMenuOneDescription.Translate(); break; case MenuCategory.CustomTwo: menu.GameSettingsTab.gameObject.SetActive(true); - menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.CustomOptionMenuTwoDescription; + menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.CustomOptionMenuTwoDescription.Translate(); break; } } diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 36651f9..0ae7982 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -2,6 +2,7 @@ using Il2CppSystem; using MiraAPI.GameOptions; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using UnityEngine; using Object = Il2CppSystem.Object; @@ -162,7 +163,7 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()); + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()).Translate(); return false; } @@ -200,7 +201,7 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()); + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()).Translate(); return false; } @@ -307,7 +308,7 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()); + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()).Translate(); __instance.ValueText.text = TranslationController.Instance.GetString(__instance.Values[__instance.Value], Array.Empty()); return false; diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 0dc8721..174b6f8 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -8,6 +8,7 @@ using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -185,11 +186,12 @@ public static class RoleSettingMenuPatches RoleGroupHidden.TryAdd(group, false); - var name = group.Name switch + var translatedName = group.Name.Translate(); + var name = translatedName switch { "Crewmate" => StringNames.CrewmateRolesHeader, "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(group.Name), + _ => CustomStringName.CreateAndRegister(translatedName), }; var categoryHeaderMasked = Object.Instantiate( @@ -264,8 +266,8 @@ public static class RoleSettingMenuPatches quotaInst.gameObject.SetActive(!RoleGroupHidden[group]); var label = RoleGroupHidden[group] - ? "(Click to open)" - : "(Click to close)"; + ? $"({"gamesetting.global.clicktoopen".Translate()})" + : $"({"gamesetting.global.clicktoclose".Translate()})"; var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); newText.text = $"{label}"; newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); @@ -524,7 +526,7 @@ public static class RoleSettingMenuPatches return; } - __instance.roleDescriptionText.text = customRole.RoleLongDescription; + __instance.roleDescriptionText.text = customRole.RoleLongDescription.Translate(); __instance.roleTitleText.text = role.GetRoleName(); var imgBg = __instance.AdvancedRolesSettings.transform.FindChild("Imagebackground"); @@ -563,7 +565,7 @@ public static class RoleSettingMenuPatches comp.Destroy(); } - categoryHeaderMasked.Title.text = "RETURN TO ROLE SETTINGS"; + categoryHeaderMasked.Title.text = "miraApi.returnToRoleSettings".Translate(); if (!categoryHeaderMasked.gameObject.TryGetComponent(out _)) { diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index e662859..abeaf12 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -20,6 +20,7 @@ using MiraAPI.Modifiers; using MiraAPI.Patches.Options; using MiraAPI.Presets; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Networking; using Reactor.Utilities; @@ -423,7 +424,7 @@ public sealed class MiraPluginManager KeybindManager.Keybinds.Add(keybind); if (source is IMiraPlugin miraPlugin) { - keybind.SourcePluginName = miraPlugin.OptionsTitleText; + keybind.SourcePluginName = miraPlugin.OptionsTitleText.Translate(); } else if (source is MiraApiPlugin) { diff --git a/MiraAPI/Resources/Translations/English.xml b/MiraAPI/Resources/Translations/English.xml new file mode 100644 index 0000000..79630d5 --- /dev/null +++ b/MiraAPI/Resources/Translations/English.xml @@ -0,0 +1,28 @@ + + + Mira API + Visuals/UI + Freeplay + Accessibility + MiraAPI + Show Modifiers HUD on Left Side + Show Keybinds on Buttons + Button Scale Factor + Max FPS + Pretty Task Laptop + Enable Sabotage Flashes + Enable Sabotage Blare + RETURN TO ROLE SETTINGS + Custom Category 1 + Custom Category 2 + Configure modifiers and their settings here! + Apply game settings for this mod! + Default + Default Among Us GameMode + Click to close + Click to open + Select or create a options preset for this game + Save + Roles + Modifiers + diff --git a/MiraAPI/Resources/Translations/SChinese.xml b/MiraAPI/Resources/Translations/SChinese.xml new file mode 100644 index 0000000..17df36e --- /dev/null +++ b/MiraAPI/Resources/Translations/SChinese.xml @@ -0,0 +1,27 @@ + + + Mira API + 界面/视觉 + 自由模式 + 无障碍 + MiraAPI + 在左侧显示附加职业UI + 在按钮上显示快捷键 + 按钮缩放比例 + 最大帧数 + 美化任务笔记本 + 启用破坏红色闪烁 + 启用破坏音效 + 返回职业设置 + 自定义类别 1 + 自定义类别 2 + 在此配置附加职业及其设置! + 为此模组应用游戏设置! + 默认 + 默认原版游戏模式 + 点击关闭 + 为本游戏选择或创建一个预设 + 保存 + 职业 + 附加职业 + diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 6c6760b..d02f50f 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -7,6 +7,7 @@ using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Networking; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -131,9 +132,9 @@ public static class CustomRoleManager roleBehaviour.Role = (RoleTypes)roleId; roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Custom ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; - roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName); - roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription); - roleBehaviour.BlurbNameLong = CustomStringName.CreateAndRegister(customRole.RoleLongDescription); + roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName.Translate()); + roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription.Translate()); + roleBehaviour.BlurbNameLong = CustomStringName.CreateAndRegister(customRole.RoleLongDescription.Translate()); roleBehaviour.AffectedByLightAffectors = customRole.Configuration.AffectedByLightOnAirship; roleBehaviour.CanBeKilled = customRole.Configuration.CanGetKilled; roleBehaviour.CanUseKillButton = customRole.Configuration.UseVanillaKillButton; @@ -178,7 +179,7 @@ public static class CustomRoleManager if (useTaskHint && !overridesTaskText) { - Error($"Role {customRole.RoleName} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!"); + Error($"Role {customRole.RoleName.Translate()} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!"); } CustomRoles.Add(roleId, roleBehaviour); @@ -246,9 +247,9 @@ public static class CustomRoleManager { var tabText = panel.tab.gameObject.GetComponentInChildren(); var ogPanel = HudManager.Instance.TaskStuff.transform.FindChild("TaskPanel").gameObject.GetComponent(); - if (tabText.text != role.RoleName) + if (tabText.text != role.RoleName.Translate()) { - tabText.text = role.RoleName; + tabText.text = role.RoleName.Translate(); } var y = ogPanel.taskText.textBounds.size.y + 1; diff --git a/MiraAPI/Roles/CustomRoleUtils.cs b/MiraAPI/Roles/CustomRoleUtils.cs index 08c5cd4..f0bfe4e 100644 --- a/MiraAPI/Roles/CustomRoleUtils.cs +++ b/MiraAPI/Roles/CustomRoleUtils.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Text; using AmongUs.GameOptions; +using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using UnityEngine; @@ -101,9 +102,9 @@ public static class CustomRoleUtils public static StringBuilder CreateForRole(ICustomRole role) { var taskStringBuilder = new StringBuilder(); - taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is {role.RoleName}."); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is {role.RoleName.Translate()}."); taskStringBuilder.Append(""); - taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}"); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription.Translate()}"); return taskStringBuilder; } diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index c0c97be..2df32ae 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -4,6 +4,7 @@ using BepInEx.Configuration; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using UnityEngine; namespace MiraAPI.Roles; @@ -56,7 +57,7 @@ public interface ICustomRole : IOptionable ModdedRoleTeams.Crewmate => RoleOptionsGroup.Crewmate, ModdedRoleTeams.Impostor => RoleOptionsGroup.Impostor, ModdedRoleTeams.Custom => RoleOptionsGroup.Neutral, - _ => new RoleOptionsGroup(RoleName, RoleColor), + _ => new RoleOptionsGroup(RoleName.Translate(), RoleColor), }; /// @@ -156,7 +157,7 @@ public interface ICustomRole : IOptionable { if (!Configuration.CanModifyChance) { - Error($"Cannot modify chance for role: {RoleName}"); + Error($"Cannot modify chance for role: {RoleName.Translate()}"); return; } @@ -166,7 +167,7 @@ public interface ICustomRole : IOptionable return; } - Error($"Error getting chance configuration for role: {RoleName}"); + Error($"Error getting chance configuration for role: {RoleName.Translate()}"); } /// @@ -181,7 +182,7 @@ public interface ICustomRole : IOptionable return; } - Error($"Error getting count configuration for role: {RoleName}"); + Error($"Error getting count configuration for role: {RoleName.Translate()}"); } /// @@ -221,7 +222,7 @@ public interface ICustomRole : IOptionable /// A string with a custom ejection message or . string? GetCustomEjectionMessage(NetworkedPlayerInfo player) { - return Team == ModdedRoleTeams.Impostor ? $"{player.PlayerName} was The {RoleName}" : null; + return Team == ModdedRoleTeams.Impostor ? $"{player.PlayerName} was The {RoleName.Translate()}" : null; } /// diff --git a/MiraAPI/Translation/MiraLanguage.cs b/MiraAPI/Translation/MiraLanguage.cs new file mode 100644 index 0000000..2027a9d --- /dev/null +++ b/MiraAPI/Translation/MiraLanguage.cs @@ -0,0 +1,24 @@ +namespace MiraAPI.Translation; + +/// +/// Supported languages for Mira translation system. +/// Maps to Among Us's SupportedLangs enum values. +/// +public enum MiraLanguage +{ + English, + Spanish, + Korean, + Russian, + Portuguese, + Arabic, + Filipino, + Polish, + SChinese, + TChinese, + French, + German, + Japanese, + Dutch, + Italian, +} diff --git a/MiraAPI/Translation/TranslationExtensions.cs b/MiraAPI/Translation/TranslationExtensions.cs new file mode 100644 index 0000000..60b367a --- /dev/null +++ b/MiraAPI/Translation/TranslationExtensions.cs @@ -0,0 +1,15 @@ +namespace MiraAPI.Translation; + +/// +/// Extension method for translating strings. +/// +public static class TranslationExtensions +{ + /// + /// Translates a key to the current language. + /// + public static string Translate(this string key) + { + return TranslationManager.Translate(key); + } +} diff --git a/MiraAPI/Translation/TranslationManager.cs b/MiraAPI/Translation/TranslationManager.cs new file mode 100644 index 0000000..eaba44d --- /dev/null +++ b/MiraAPI/Translation/TranslationManager.cs @@ -0,0 +1,205 @@ +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>> Translations = []; + + /// + /// Gets the current language from Among Us settings. + /// + public static MiraLanguage CurrentLanguage + { + get + { + try + { + var langName = AmongUs.Data.DataManager.Settings.Language.CurrentLanguage.ToString(); + return Enum.TryParse(langName, out var result) ? result : MiraLanguage.English; + } + catch + { + return MiraLanguage.English; + } + } + } + + /// + /// 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(). + /// + /// The mod GUID (e.g., "mira.example"). + 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); + } + + /// + /// Translates a key into the current language. + /// Searches all mods, with reverse lookup fallback. + /// + 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; + } + + 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(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 ParseXmlFile(string filePath) + { + var dict = new Dictionary(); + 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("", "\n").Replace("", "&"); + + dict[name] = value; + } + + return dict; + } +} diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 9f66817..54f1674 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -5,6 +5,7 @@ using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using Rewired; using TMPro; @@ -536,7 +537,7 @@ public static class Helpers { if (role is ICustomRole custom) { - return custom.RoleName; + return custom.RoleName.Translate(); } return role.NiceName; } From 525f987926f808ed06662d4ad6bf8f19c7e69c8f Mon Sep 17 00:00:00 2001 From: linmeideli <2558527373@qq.com> Date: Fri, 14 Aug 2026 13:38:28 +0800 Subject: [PATCH 02/16] this check does not work for Unity's fake null --- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index aa5156e..0aea27b 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -126,7 +126,7 @@ internal static class GameOptionsMenuPatch private static void UpdateGroup(AbstractOptionGroup? group, ref float num) { - if (group is null || !group.Ready || group.Options.Count == 0 || group.Header is null) + if (group is null || !group.Ready || group.Options.Count == 0 || group.Header == null) { return; } From acfe345133d2c40232a214bf1b282d0465ba879f Mon Sep 17 00:00:00 2001 From: linmeideli <2558527373@qq.com> Date: Tue, 18 Aug 2026 21:25:22 +0800 Subject: [PATCH 03/16] enhanced --- .../Resources/Translations/English.xml | 20 +++++++++---------- .../Resources/Translations/SChinese.xml | 20 +++++++++---------- MiraAPI.Example/Roles/ChameleonRole.cs | 4 +--- MiraAPI.Example/Roles/FreezerRole.cs | 4 +--- MiraAPI.Example/Roles/MayorRole.cs | 4 +--- MiraAPI.Example/Roles/NeutralKillerRole.cs | 4 +--- MiraAPI.Example/Roles/TeleporterRole.cs | 4 +--- MiraAPI/Roles/ICustomRole.cs | 11 +++++++--- MiraAPI/Translation/TranslationManager.cs | 5 +++++ 9 files changed, 38 insertions(+), 38 deletions(-) diff --git a/MiraAPI.Example/Resources/Translations/English.xml b/MiraAPI.Example/Resources/Translations/English.xml index f6256c0..fd429f8 100644 --- a/MiraAPI.Example/Resources/Translations/English.xml +++ b/MiraAPI.Example/Resources/Translations/English.xml @@ -1,15 +1,15 @@ - Teleporter - Zoom out and teleport across the map! - Freezer - Freeze another player for a duration of time. - Mayor - You get extra votes! - Chamelon - Stay invisible while not moving. - Outcast Killer - Outcast who can kill. + Teleporter + Zoom out and teleport across the map! + Freezer + Freeze another player for a duration of time. + Mayor + You get extra votes! + Chamelon + Stay invisible while not moving. + Outcast Killer + Outcast who can kill. Outcast OUTCAST You are an Outcast. You do not have a team. diff --git a/MiraAPI.Example/Resources/Translations/SChinese.xml b/MiraAPI.Example/Resources/Translations/SChinese.xml index 68f34e9..3b5a53e 100644 --- a/MiraAPI.Example/Resources/Translations/SChinese.xml +++ b/MiraAPI.Example/Resources/Translations/SChinese.xml @@ -1,15 +1,15 @@ - 传送者 - 缩小视野并在地图上传送! - 冰冻者 - 将其他玩家冰冻一段时间。 - 市长 - 你拥有额外投票权! - 变色龙 - 静止不动时保持隐身。 - 流放杀手 - 可以击杀的流放者。 + 传送者 + 缩小视野并在地图上传送! + 冰冻者 + 将其他玩家冰冻一段时间。 + 市长 + 你拥有额外投票权! + 变色龙 + 静止不动时保持隐身。 + 流放杀手 + 可以击杀的流放者。 流放者 流放者 你是流放者,你没有队伍。 diff --git a/MiraAPI.Example/Roles/ChameleonRole.cs b/MiraAPI.Example/Roles/ChameleonRole.cs index bd733d7..d396fe6 100644 --- a/MiraAPI.Example/Roles/ChameleonRole.cs +++ b/MiraAPI.Example/Roles/ChameleonRole.cs @@ -10,9 +10,7 @@ namespace MiraAPI.Example.Roles; public class ChameloenRole : CrewmateRole, ICustomRole { - public string RoleName => "chamelon.name"; - public string RoleLongDescription => "chamelon.description"; - public string RoleDescription => RoleLongDescription; + public string IdPart => "chamelon"; public Color RoleColor => Palette.AcceptedGreen; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs index 96adc80..33656c0 100644 --- a/MiraAPI.Example/Roles/FreezerRole.cs +++ b/MiraAPI.Example/Roles/FreezerRole.cs @@ -6,9 +6,7 @@ namespace MiraAPI.Example.Roles; public class FreezerRole : ImpostorRole, ICustomRole { - public string RoleName => "freezer.name"; - public string RoleLongDescription => "freezer.description"; - public string RoleDescription => RoleLongDescription; + public string IdPart => "freezer"; public Color RoleColor => Palette.Blue; public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; diff --git a/MiraAPI.Example/Roles/MayorRole.cs b/MiraAPI.Example/Roles/MayorRole.cs index 12effc0..b06d350 100644 --- a/MiraAPI.Example/Roles/MayorRole.cs +++ b/MiraAPI.Example/Roles/MayorRole.cs @@ -5,9 +5,7 @@ namespace MiraAPI.Example.Roles; public class MayorRole : CrewmateRole, ICustomRole { - public string RoleName => "mayor.name"; - public string RoleLongDescription => "mayor.description"; - public string RoleDescription => RoleLongDescription; + public string IdPart => "mayor"; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public CustomRoleConfiguration Configuration => new(this); diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index 695f4df..4202cd1 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -8,9 +8,7 @@ namespace MiraAPI.Example.Roles; public class NeutralKillerRole : ImpostorRole, ICustomRole { - public string RoleName => "outcastKiller.name"; - public string RoleDescription => "outcastKiller.description"; - public string RoleLongDescription => RoleDescription; + public string IdPart => "outcastKiller"; public Color RoleColor => Color.magenta; public ModdedRoleTeams Team => ModdedRoleTeams.Custom; diff --git a/MiraAPI.Example/Roles/TeleporterRole.cs b/MiraAPI.Example/Roles/TeleporterRole.cs index b60407b..7136ff0 100644 --- a/MiraAPI.Example/Roles/TeleporterRole.cs +++ b/MiraAPI.Example/Roles/TeleporterRole.cs @@ -5,9 +5,7 @@ namespace MiraAPI.Example.Roles; public class TeleporterRole : CrewmateRole, ICustomRole { - public string RoleName => "teleporter.name"; - public string RoleLongDescription => "teleporter.description"; - public string RoleDescription => RoleLongDescription; + public string IdPart => "teleporter"; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 2df32ae..0b3f53b 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -14,20 +14,25 @@ namespace MiraAPI.Roles; /// public interface ICustomRole : IOptionable { + /// + /// Gets the id part used to build the role's translation keys. + /// + string IdPart => GetType().Name.ToLower(); + /// /// Gets the name of the role. /// - string RoleName { get; } + string RoleName => TranslationManager.BuildTranslationId(IdPart, "name"); /// /// Gets the description of the role. Used in the Intro Cutscene. /// - string RoleDescription { get; } + string RoleDescription => TranslationManager.BuildTranslationId(IdPart, "description"); /// /// Gets the long description of the role. Used in the Role Tab and Role Options. /// - string RoleLongDescription { get; } + string RoleLongDescription => TranslationManager.BuildTranslationId(IdPart, "description"); /// /// Gets the of the role. diff --git a/MiraAPI/Translation/TranslationManager.cs b/MiraAPI/Translation/TranslationManager.cs index eaba44d..a6fbe63 100644 --- a/MiraAPI/Translation/TranslationManager.cs +++ b/MiraAPI/Translation/TranslationManager.cs @@ -119,6 +119,11 @@ public static class TranslationManager 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); From 6a7f58effa6b60aac35fcc51d48eaf2c989344c4 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:23:57 -0500 Subject: [PATCH 04/16] Add localization for gamemodes --- MiraAPI/GameModes/ClassicMode.cs | 4 +- MiraAPI/GameModes/HideAndSeekMode.cs | 4 +- MiraAPI/Resources/Translations/English.xml | 54 ++++++++++++---------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/MiraAPI/GameModes/ClassicMode.cs b/MiraAPI/GameModes/ClassicMode.cs index 44b43da..d434abc 100644 --- a/MiraAPI/GameModes/ClassicMode.cs +++ b/MiraAPI/GameModes/ClassicMode.cs @@ -11,10 +11,10 @@ namespace MiraAPI.GameModes; public class ClassicMode : AbstractGameMode { /// - public override string Name => "Classic"; + public override string Name => "gamemode.classic.name"; /// - public override string Description => "The classic Among Us experience!\nFind the Impostors or deceive the Crew."; + public override string Description => "gamemode.classic.description"; public override LoadableAsset? Icon => MiraAssets.ClassicGamemodeIcon; } diff --git a/MiraAPI/GameModes/HideAndSeekMode.cs b/MiraAPI/GameModes/HideAndSeekMode.cs index d5c90e8..8c332ed 100644 --- a/MiraAPI/GameModes/HideAndSeekMode.cs +++ b/MiraAPI/GameModes/HideAndSeekMode.cs @@ -25,10 +25,10 @@ namespace MiraAPI.GameModes; public class HideAndSeekMode : AbstractGameMode { /// - public override string Name => "Hide n Seek"; + public override string Name => "gamemode.hidenseek.name"; /// - public override string Description => "You can run, but you can't hide!\nOutlive the timer or eliminate the crew."; + public override string Description => "Outlive the timer or eliminate the crew"; // TODO: Once the Hide n Seek port is ready for release, we will remove this. public override bool HideMode => !MiraApiPlugin.IsDevBuild; diff --git a/MiraAPI/Resources/Translations/English.xml b/MiraAPI/Resources/Translations/English.xml index 79630d5..8dc539e 100644 --- a/MiraAPI/Resources/Translations/English.xml +++ b/MiraAPI/Resources/Translations/English.xml @@ -1,28 +1,32 @@ - Mira API - Visuals/UI - Freeplay - Accessibility - MiraAPI - Show Modifiers HUD on Left Side - Show Keybinds on Buttons - Button Scale Factor - Max FPS - Pretty Task Laptop - Enable Sabotage Flashes - Enable Sabotage Blare - RETURN TO ROLE SETTINGS - Custom Category 1 - Custom Category 2 - Configure modifiers and their settings here! - Apply game settings for this mod! - Default - Default Among Us GameMode - Click to close - Click to open - Select or create a options preset for this game - Save - Roles - Modifiers + Mira API + Visuals/UI + Freeplay + Accessibility + MiraAPI + Show Modifiers HUD on Left Side + Show Keybinds on Buttons + Button Scale Factor + Max FPS + Pretty Task Laptop + Enable Sabotage Flashes + Enable Sabotage Blare + RETURN TO ROLE SETTINGS + Custom Category 1 + Custom Category 2 + Configure modifiers and their settings here! + Apply game settings for this mod! + Classic + The classic Among Us experience! +Find the Impostors or deceive the Crew. + Hide n Seek + You can run, but you can't hide! +Outlive the timer or eliminate the crew. + Click to close + Click to open + Select or create a options preset for this game + Save + Roles + Modifiers From 6da1048d09e59439760e17c475968a5266dc20c2 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:04:09 -0500 Subject: [PATCH 05/16] Adjust system for Crowdin / Weblate support --- MiraAPI/MiraApiPlugin.cs | 10 +- .../English.xml => Locale/en_US.xml} | 6 + .../SChinese.xml => Locale/zh_CN.xml} | 0 MiraAPI/Roles/ICustomRole.cs | 6 +- MiraAPI/Translation/MiraLanguage.cs | 22 +- MiraAPI/Translation/MiraLocaleManager.cs | 270 ++++++++++++++++++ .../Translation/MiraLocalizationProvider.cs | 61 ++++ MiraAPI/Translation/TranslationExtensions.cs | 2 +- MiraAPI/Translation/TranslationManager.cs | 210 -------------- .../Components/CenteredGridArrange.cs | 4 +- 10 files changed, 366 insertions(+), 225 deletions(-) rename MiraAPI/Resources/{Translations/English.xml => Locale/en_US.xml} (85%) rename MiraAPI/Resources/{Translations/SChinese.xml => Locale/zh_CN.xml} (100%) create mode 100644 MiraAPI/Translation/MiraLocaleManager.cs create mode 100644 MiraAPI/Translation/MiraLocalizationProvider.cs delete mode 100644 MiraAPI/Translation/TranslationManager.cs diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index c841fae..e6a0148 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -1,5 +1,6 @@ global using static Reactor.Utilities.Logger; 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; } + /// + /// Gets the specified Culture for string manipulations. + /// + public static CultureInfo Culture { get; internal set; } = new("en-US"); + /// 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 diff --git a/MiraAPI/Resources/Translations/English.xml b/MiraAPI/Resources/Locale/en_US.xml similarity index 85% rename from MiraAPI/Resources/Translations/English.xml rename to MiraAPI/Resources/Locale/en_US.xml index 8dc539e..be6b023 100644 --- a/MiraAPI/Resources/Translations/English.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -29,4 +29,10 @@ Outlive the timer or eliminate the crew. Save Roles Modifiers + Crewmate + Crew + Impostor + Imp + Neutral + Neut diff --git a/MiraAPI/Resources/Translations/SChinese.xml b/MiraAPI/Resources/Locale/zh_CN.xml similarity index 100% rename from MiraAPI/Resources/Translations/SChinese.xml rename to MiraAPI/Resources/Locale/zh_CN.xml diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index e1b3b9f..8e233b8 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -22,12 +22,12 @@ public interface ICustomRole : IOptionable /// /// Gets the name of the role. /// - string RoleName => TranslationManager.BuildTranslationId(IdPart, "name"); + string RoleName => MiraLocaleManager.BuildTranslationId(IdPart, "name"); /// /// Gets the description of the role. Used in the Intro Cutscene. /// - string RoleDescription => TranslationManager.BuildTranslationId(IdPart, "description"); + string RoleDescription => MiraLocaleManager.BuildTranslationId(IdPart, "description"); /// /// Gets the medium description of the role. Used in the role guide. @@ -37,7 +37,7 @@ public interface ICustomRole : IOptionable /// /// Gets the long description of the role. Used in the Role Tab and Role Options. /// - string RoleLongDescription => TranslationManager.BuildTranslationId(IdPart, "description"); + string RoleLongDescription => MiraLocaleManager.BuildTranslationId(IdPart, "description"); /// /// Gets the faction / alignment of the role. Used in the role guide. diff --git a/MiraAPI/Translation/MiraLanguage.cs b/MiraAPI/Translation/MiraLanguage.cs index 2027a9d..d6d2624 100644 --- a/MiraAPI/Translation/MiraLanguage.cs +++ b/MiraAPI/Translation/MiraLanguage.cs @@ -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, } diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs new file mode 100644 index 0000000..71c4b2b --- /dev/null +++ b/MiraAPI/Translation/MiraLocaleManager.cs @@ -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>> Locale = []; + + public static Dictionary 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 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 + }; + + /// + /// Gets the current language from Among Us settings. + /// + public static MiraLanguage CurrentLanguage + { + get + { + try + { + var langName = AmongUs.Data.DataManager.Settings.Language.CurrentLanguage; + return (MiraLanguage)langName; + } + catch + { + return MiraLanguage.English; + } + } + } + + /// + /// 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(). + /// + /// The mod GUID (e.g., "mira.example"). + public static void Register(string modGuid) + { + var callingAssembly = Assembly.GetCallingAssembly(); + var dir = GetModLangDir(modGuid); + + LoadInternalStrings(callingAssembly, modGuid, dir); + } + + /// + /// 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; + + 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 ParseXmlFile(string filePath) + { + var dict = new Dictionary(); + 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("", "\n").Replace("", "&"); + + dict[name] = value; + } + + return dict; + } +} diff --git a/MiraAPI/Translation/MiraLocalizationProvider.cs b/MiraAPI/Translation/MiraLocalizationProvider.cs new file mode 100644 index 0000000..c3f81d6 --- /dev/null +++ b/MiraAPI/Translation/MiraLocalizationProvider.cs @@ -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 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($""); + Warning($""); + foreach (var stringName in TranslationController.Instance.currentLanguage.AllStrings) + { + var value = stringName.Value.Replace("\n", "\\%nl\\%"); + value = value.Replace("{", "["); + value = value.Replace("}", "]"); + Warning($"{value}"); + } + Warning($"");*/ + } +} \ No newline at end of file diff --git a/MiraAPI/Translation/TranslationExtensions.cs b/MiraAPI/Translation/TranslationExtensions.cs index 60b367a..5f0edbd 100644 --- a/MiraAPI/Translation/TranslationExtensions.cs +++ b/MiraAPI/Translation/TranslationExtensions.cs @@ -10,6 +10,6 @@ public static class TranslationExtensions /// public static string Translate(this string key) { - return TranslationManager.Translate(key); + return MiraLocaleManager.Get(key); } } diff --git a/MiraAPI/Translation/TranslationManager.cs b/MiraAPI/Translation/TranslationManager.cs deleted file mode 100644 index a6fbe63..0000000 --- a/MiraAPI/Translation/TranslationManager.cs +++ /dev/null @@ -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>> Translations = []; - - /// - /// Gets the current language from Among Us settings. - /// - public static MiraLanguage CurrentLanguage - { - get - { - try - { - var langName = AmongUs.Data.DataManager.Settings.Language.CurrentLanguage.ToString(); - return Enum.TryParse(langName, out var result) ? result : MiraLanguage.English; - } - catch - { - return MiraLanguage.English; - } - } - } - - /// - /// 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(). - /// - /// The mod GUID (e.g., "mira.example"). - 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); - } - - /// - /// Translates a key into the current language. - /// Searches all mods, with reverse lookup fallback. - /// - 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(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 ParseXmlFile(string filePath) - { - var dict = new Dictionary(); - 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("", "\n").Replace("", "&"); - - dict[name] = value; - } - - return dict; - } -} diff --git a/MiraAPI/Utilities/Components/CenteredGridArrange.cs b/MiraAPI/Utilities/Components/CenteredGridArrange.cs index ee32b26..a8d4ccd 100644 --- a/MiraAPI/Utilities/Components/CenteredGridArrange.cs +++ b/MiraAPI/Utilities/Components/CenteredGridArrange.cs @@ -9,9 +9,9 @@ namespace MiraAPI.Utilities.Components; /// /// Utility component for arranging objects in a centered layout, similar to . /// -/// +/// The for the component. [RegisterInIl2Cpp] -public class CenteredGridArrange(IntPtr ptr) : MonoBehaviour(ptr) +public class CenteredGridArrange(IntPtr iPtr) : MonoBehaviour(iPtr) { /// /// Gets or sets the cell size, which is used for spacing. From c449669cf26bac7e73466ec9076759adc38f7904 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:15:39 -0500 Subject: [PATCH 06/16] Fix example plugin error --- MiraAPI.Example/ExamplePlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index c63fe39..66e6839 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -21,7 +21,7 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin { public ExamplePlugin() { - TranslationManager.Register("mira.example"); + MiraLocaleManager.Register("mira.example"); } public Harmony Harmony { get; } = new(Id); public string OptionsTitleText => "Mira API\nExample Mod"; From 80e1e6e454e4b104e707714b0941362380a82afe Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:20:52 -0500 Subject: [PATCH 07/16] Clear some warnings --- MiraAPI.Example/ExamplePlugin.cs | 2 -- MiraAPI.Example/MeetingAbilities/EndMeetingButton.cs | 6 +----- MiraAPI.Example/MeetingAbilities/ShootButton.cs | 7 +++++-- MiraAPI.Example/MeetingAbilities/ShotgunButton.cs | 9 ++++++--- MiraAPI.Example/MiraAPI.Example.csproj | 1 + MiraAPI/GameModes/CustomGameModeManager.cs | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index 66e6839..4ff4c8e 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -4,11 +4,9 @@ using BepInEx.Unity.IL2CPP; using HarmonyLib; using MiraAPI.PluginLoading; using MiraAPI.Translation; -using MiraAPI.Utilities.Assets; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; -using UnityEngine; namespace MiraAPI.Example; diff --git a/MiraAPI.Example/MeetingAbilities/EndMeetingButton.cs b/MiraAPI.Example/MeetingAbilities/EndMeetingButton.cs index 0d39027..a1f446c 100644 --- a/MiraAPI.Example/MeetingAbilities/EndMeetingButton.cs +++ b/MiraAPI.Example/MeetingAbilities/EndMeetingButton.cs @@ -1,9 +1,5 @@ -using MiraAPI.Example.Modifiers; -using MiraAPI.Example.Roles; -using MiraAPI.Hud; -using MiraAPI.Keybinds; +using MiraAPI.Example.Roles; using MiraAPI.MeetingAbilities; -using MiraAPI.Modifiers; using MiraAPI.Utilities.Assets; using UnityEngine; diff --git a/MiraAPI.Example/MeetingAbilities/ShootButton.cs b/MiraAPI.Example/MeetingAbilities/ShootButton.cs index ad5f336..179829d 100644 --- a/MiraAPI.Example/MeetingAbilities/ShootButton.cs +++ b/MiraAPI.Example/MeetingAbilities/ShootButton.cs @@ -29,6 +29,9 @@ public class ShootButton : TargetedMeetingButton { playerVoteArea.SetDisabled(); var player = playerVoteArea.GetPlayer(); - PlayerControl.LocalPlayer.RpcCustomMurder(player); + if (player != null) + { + PlayerControl.LocalPlayer.RpcCustomMurder(player); + } } -} \ No newline at end of file +} diff --git a/MiraAPI.Example/MeetingAbilities/ShotgunButton.cs b/MiraAPI.Example/MeetingAbilities/ShotgunButton.cs index bc9f20c..906bd5d 100644 --- a/MiraAPI.Example/MeetingAbilities/ShotgunButton.cs +++ b/MiraAPI.Example/MeetingAbilities/ShotgunButton.cs @@ -1,4 +1,3 @@ -using System; using MiraAPI.MeetingAbilities; using MiraAPI.Networking; using MiraAPI.Utilities; @@ -28,6 +27,7 @@ public class ShotgunButton : MultiTargetMeetingButton protected override void OnClick(PlayerVoteArea playerVoteArea) { + // Emptu because it only triggers the actual kills once all meeting abilities have been used. } public override LoadableAsset SpriteActive => ExampleAssets.CallMeetingButton; @@ -45,7 +45,10 @@ public class ShotgunButton : MultiTargetMeetingButton { playerVoteArea.SetDisabled(); var player = playerVoteArea.GetPlayer(); - PlayerControl.LocalPlayer.RpcCustomMurder(player); + if (player != null) + { + PlayerControl.LocalPlayer.RpcCustomMurder(player); + } } } -} \ No newline at end of file +} diff --git a/MiraAPI.Example/MiraAPI.Example.csproj b/MiraAPI.Example/MiraAPI.Example.csproj index d0b23ac..675a84e 100644 --- a/MiraAPI.Example/MiraAPI.Example.csproj +++ b/MiraAPI.Example/MiraAPI.Example.csproj @@ -3,6 +3,7 @@ 1.0.0 Example mod for Mira API Angxl, XtraCube + true diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index b47220a..254bf6d 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -59,7 +59,7 @@ public static class CustomGameModeManager /// /// Checks to see if the current game mode is an instance of the HNS mode. /// - /// True if the Hide & Seek mode is the current one. + /// True if the Hide n Seek mode is the current one. public static bool IsHideNSeek() => ActiveMode != null && ActiveMode.GetType().IsAssignableTo(typeof(HideAndSeekMode)); /// From 47582897a88104dcc4157e60ef3a79ce9b9c516b Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:47:54 -0500 Subject: [PATCH 08/16] Fix loading errors --- MiraAPI/GameModes/AbstractGameMode.cs | 3 ++- MiraAPI/GameModes/ClassicMode.cs | 2 +- MiraAPI/GameModes/HideAndSeekMode.cs | 4 ++-- MiraAPI/MiraApiPlugin.cs | 2 +- MiraAPI/Patches/Menu/NameplatesTabPatches.cs | 2 -- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 2 +- MiraAPI/PluginLoading/MiraPluginManager.cs | 17 +---------------- MiraAPI/Resources/Locale/en_US.xml | 4 ++-- MiraAPI/Translation/MiraLocaleManager.cs | 3 --- MiraAPI/Translation/MiraLocalizationProvider.cs | 6 +++--- 10 files changed, 13 insertions(+), 32 deletions(-) diff --git a/MiraAPI/GameModes/AbstractGameMode.cs b/MiraAPI/GameModes/AbstractGameMode.cs index 6fa2d8c..37bbd39 100644 --- a/MiraAPI/GameModes/AbstractGameMode.cs +++ b/MiraAPI/GameModes/AbstractGameMode.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using AmongUs.GameOptions; using MiraAPI.GameOptions; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; using TMPro; @@ -49,7 +50,7 @@ public abstract class AbstractGameMode : IOptionable /// /// Gets the colored game mode name, using the color and name properties. /// - public string ColoredName => $"{this.Name}"; + public string ColoredName => $"{MiraLocaleManager.Get(Name)}"; /// /// Gets the game mode id. diff --git a/MiraAPI/GameModes/ClassicMode.cs b/MiraAPI/GameModes/ClassicMode.cs index d434abc..6a8ea1d 100644 --- a/MiraAPI/GameModes/ClassicMode.cs +++ b/MiraAPI/GameModes/ClassicMode.cs @@ -11,7 +11,7 @@ namespace MiraAPI.GameModes; public class ClassicMode : AbstractGameMode { /// - public override string Name => "gamemode.classic.name"; + public override string Name => "gamemode.classic"; /// public override string Description => "gamemode.classic.description"; diff --git a/MiraAPI/GameModes/HideAndSeekMode.cs b/MiraAPI/GameModes/HideAndSeekMode.cs index 8c332ed..e30ab9b 100644 --- a/MiraAPI/GameModes/HideAndSeekMode.cs +++ b/MiraAPI/GameModes/HideAndSeekMode.cs @@ -25,10 +25,10 @@ namespace MiraAPI.GameModes; public class HideAndSeekMode : AbstractGameMode { /// - public override string Name => "gamemode.hidenseek.name"; + public override string Name => "gamemode.hidenseek"; /// - public override string Description => "Outlive the timer or eliminate the crew"; + public override string Description => "gamemode.hidenseek.description"; // TODO: Once the Hide n Seek port is ready for release, we will remove this. public override bool HideMode => !MiraApiPlugin.IsDevBuild; diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index e6a0148..ef9b2b9 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -77,8 +77,8 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin PluginManager = new MiraPluginManager(); PluginManager.Initialize(this, this); - LocalizationManager.Register(new MiraLocalizationProvider()); MiraLocaleManager.Register("mira.api"); + LocalizationManager.Register(new MiraLocalizationProvider()); IL2CPPChainloader.Instance.Finished += ModCompatibility diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index 0092103..6e96d5f 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -7,8 +7,6 @@ using Reactor.Utilities.Extensions; using System; using System.Collections.Generic; using System.Linq; -using MiraAPI.Utilities.Assets; -using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; using Object = UnityEngine.Object; diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 12f293c..bfaf1c4 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -114,7 +114,7 @@ internal static class GameOptionsMenuPatch _modIcon.gameObject.SetActive(false); } - _gamemodeDescription.text = gameMode.Description; + _gamemodeDescription.text = MiraLocaleManager.Get(gameMode.Description); if (gameMode.ShowNormalGameSettings) { foreach (var opt in _vanillaHeaders) diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 3b1ffa6..1a3fe4b 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -151,7 +151,7 @@ public sealed class MiraPluginManager IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => { - if (plugin is not IMiraPlugin miraPlugin) + if (plugin is not IMiraPlugin miraPlugin || pluginInfo.Metadata.GUID == MiraApiPlugin.Id) { return; } @@ -272,21 +272,6 @@ public sealed class MiraPluginManager PluginsWithOptionsOrGameModes = [..RegisteredPlugins.Where(m => m.MiraPlugin.DisplayOnOptionsMenu || m.GameModes.Count > 0)]; ModifierManager.Modifiers = new ReadOnlyCollection(ModifierManager.InternalModifiers); - - var dict = new Dictionary(); - foreach (var (key, value) in CustomGameModeManager.IdToModeMap) - { - if (value is ClassicMode) - { - continue; - } - dict.Add(value.Name, key); - if (((AmongUs.GameOptions.GameModes) key) == AmongUs.GameOptions.GameModes.Normal) - return; - // 'Default' was still being registered twice and idk why - GameModesHelpers.ModeToName.Add((AmongUs.GameOptions.GameModes)key, CustomStringName.CreateAndRegister(value.Name)); - } - EnumInjector.InjectEnumValues(dict); }; RegisterKeybinds(typeof(MiraGlobalKeybinds), PluginSingleton.Instance); diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index be6b023..7f3a2b0 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -17,10 +17,10 @@ Custom Category 2 Configure modifiers and their settings here! Apply game settings for this mod! - Classic + Classic The classic Among Us experience! Find the Impostors or deceive the Crew. - Hide n Seek + Hide n Seek You can run, but you can't hide! Outlive the timer or eliminate the crew. Click to close diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs index 71c4b2b..5914c07 100644 --- a/MiraAPI/Translation/MiraLocaleManager.cs +++ b/MiraAPI/Translation/MiraLocaleManager.cs @@ -3,10 +3,7 @@ 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; diff --git a/MiraAPI/Translation/MiraLocalizationProvider.cs b/MiraAPI/Translation/MiraLocalizationProvider.cs index c3f81d6..20b80ed 100644 --- a/MiraAPI/Translation/MiraLocalizationProvider.cs +++ b/MiraAPI/Translation/MiraLocalizationProvider.cs @@ -13,7 +13,7 @@ public class MiraLocalizationProvider : LocalizationProvider public override bool TryGetText(StringNames stringName, out string? result) { - if ((int)stringName < 0 && _reactorProvider!.TryGetText(stringName, out var reactorText)) + if (_reactorProvider != null && (int)stringName < 0 && _reactorProvider.TryGetText(stringName, out var reactorText)) { if (reactorText.IsNullOrWhiteSpace()) { @@ -21,7 +21,7 @@ public class MiraLocalizationProvider : LocalizationProvider return true; } var localeText = MiraLocaleManager.Get(reactorText!); - if (!localeText.Contains("STRMISS")) + if (localeText != reactorText) { result = localeText; return true; @@ -58,4 +58,4 @@ public class MiraLocalizationProvider : LocalizationProvider } Warning($"");*/ } -} \ No newline at end of file +} From 5b006ab7d3cdc10f3dd7d05ea1777938060faa44 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:04:11 -0500 Subject: [PATCH 09/16] Fix xml loading --- MiraAPI/Translation/MiraLocaleManager.cs | 67 +++++++++++++++--------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs index 5914c07..ee6b1d8 100644 --- a/MiraAPI/Translation/MiraLocaleManager.cs +++ b/MiraAPI/Translation/MiraLocaleManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Xml.Linq; +using System.Xml; using UnityEngine; namespace MiraAPI.Translation; @@ -180,12 +180,18 @@ public static class MiraLocaleManager private static void LoadInternalStrings(Assembly assembly, string modGuid, string dir) { Directory.CreateDirectory(dir); + + if (!Locale.ContainsKey(modGuid)) + { + Locale[modGuid] = []; + } + var resourcePrefix = $"{assembly.GetName().Name}.Resources.Locale."; foreach (var locale in LangList) { using var resourceStream = - assembly.GetManifestResourceStream(resourcePrefix + locale.Value); + assembly.GetManifestResourceStream(resourcePrefix + locale.Value + ".xml"); if (resourceStream == null) { // Silently skipped @@ -202,7 +208,7 @@ public static class MiraLocaleManager } catch (Exception e) { - Error($"Failed to load translation {resourcePrefix}.{locale.Value}: {e.Message}"); + Error($"Failed to load translation {resourcePrefix}{locale.Value}: {e.Message}"); } } @@ -233,33 +239,46 @@ public static class MiraLocaleManager } } - private static Dictionary ParseXmlFile(string filePath) + private static Dictionary ParseXmlFile(string xmlContent) { var dict = new Dictionary(); - var doc = XDocument.Load(filePath); - - if (doc.Root == null) return dict; - - foreach (var element in doc.Root.Elements("string")) + XmlDocument xmlDoc = new(); + try { - var name = element.Attribute("name")?.Value; - var value = element.Value; + xmlDoc.LoadXml(xmlContent); + XmlNodeList? stringNodes = xmlDoc.SelectNodes("/resources/string"); - if (string.IsNullOrEmpty(name)) continue; - - if (value.Contains('[')) + if (stringNodes != null) { - value = value.Replace("[", "<"); + foreach (XmlNode node in stringNodes) + { + if (node.Attributes?["name"] != null) + { + string name = node.Attributes["name"]!.Value; + string value = node.InnerText; + + if (string.IsNullOrEmpty(name)) continue; + + if (value.Contains('[')) + { + value = value.Replace("[", "<"); + } + + if (value.Contains(']')) + { + value = value.Replace("]", ">"); + } + + value = value.Replace("", "\n").Replace("", "&"); + + dict[name] = value; + } + } } - - if (value.Contains(']')) - { - value = value.Replace("]", ">"); - } - - value = value.Replace("", "\n").Replace("", "&"); - - dict[name] = value; + } + catch (XmlException ex) + { + Error($"XML parsing error: {ex.Message}"); } return dict; From 34148499b71752a8a90ea45af08f8e5b92ac94f8 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 18:26:54 -0500 Subject: [PATCH 10/16] Fix up system registration for the most part --- .../English.xml => Locale/en_US.xml} | 0 .../SChinese.xml => Locale/zh_CN.xml} | 0 MiraAPI/Colors/CustomColor.cs | 6 +++--- MiraAPI/Colors/PaletteManager.cs | 5 +---- MiraAPI/GameModes/GameModeOption.cs | 9 +++++---- .../OptionTypes/ModdedEnumOption.cs | 9 +++++---- .../GameOptions/OptionTypes/ModdedOption.cs | 2 +- .../OptionTypes/ModdedStringOption.cs | 5 +++-- .../SettingTypes/LocalToggleSetting.cs | 2 +- .../Patches/Options/GameOptionsMenuPatch.cs | 4 ++-- .../Patches/Options/LobbyViewPanePatches.cs | 13 +++---------- .../Patches/Options/RoleSettingMenuPatches.cs | 10 ++-------- MiraAPI/Resources/Locale/en_US.xml | 10 +++++++--- MiraAPI/Roles/CustomRoleManager.cs | 7 ++++--- MiraAPI/Roles/ICustomRole.cs | 13 +++++++------ MiraAPI/Roles/RoleOptionsGroup.cs | 6 +++--- MiraAPI/Translation/MiraLocaleManager.cs | 19 ++++++++++++++++++- .../Translation/MiraLocalizationProvider.cs | 15 +++------------ 18 files changed, 68 insertions(+), 67 deletions(-) rename MiraAPI.Example/Resources/{Translations/English.xml => Locale/en_US.xml} (100%) rename MiraAPI.Example/Resources/{Translations/SChinese.xml => Locale/zh_CN.xml} (100%) diff --git a/MiraAPI.Example/Resources/Translations/English.xml b/MiraAPI.Example/Resources/Locale/en_US.xml similarity index 100% rename from MiraAPI.Example/Resources/Translations/English.xml rename to MiraAPI.Example/Resources/Locale/en_US.xml diff --git a/MiraAPI.Example/Resources/Translations/SChinese.xml b/MiraAPI.Example/Resources/Locale/zh_CN.xml similarity index 100% rename from MiraAPI.Example/Resources/Translations/SChinese.xml rename to MiraAPI.Example/Resources/Locale/zh_CN.xml diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs index 89663d1..f1b5c49 100644 --- a/MiraAPI/Colors/CustomColor.cs +++ b/MiraAPI/Colors/CustomColor.cs @@ -1,5 +1,5 @@ -using MiraAPI.Utilities; -using Reactor.Localization.Utilities; +using MiraAPI.Translation; +using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Colors; @@ -43,7 +43,7 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha } /// - public CustomColor(string name, Color32 mainColor, Color32 shadowColor) : this(CustomStringName.CreateAndRegister(name), mainColor, shadowColor) + public CustomColor(string name, Color32 mainColor, Color32 shadowColor) : this(MiraLocaleManager.GetOrCreateLocaleString(name), mainColor, shadowColor) { } } diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index c8ae990..3d25b33 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; -using MiraAPI.Translation; -using Reactor.Localization.Utilities; namespace MiraAPI.Colors; @@ -23,8 +21,7 @@ public static class PaletteManager var colors = CustomColors.Select(x => x.MainColor).ToArray(); var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray(); var stringNames = CustomColors.Select(x => - CustomStringName.CreateAndRegister(TranslationController.Instance.GetString(x.Name).Translate()) - ).ToArray(); + x.Name).ToArray(); Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors); Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors); diff --git a/MiraAPI/GameModes/GameModeOption.cs b/MiraAPI/GameModes/GameModeOption.cs index 95acd9c..180103d 100644 --- a/MiraAPI/GameModes/GameModeOption.cs +++ b/MiraAPI/GameModes/GameModeOption.cs @@ -6,6 +6,7 @@ using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Patches.GameModes; using MiraAPI.Patches.Options; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -48,17 +49,17 @@ public static class GameModeOption internal static StringOption OptionBehaviour { get; set; } = null!; internal static int _lastValue; - internal static readonly StringNames GamemodeName = CustomStringName.CreateAndRegister("Gamemode"); - internal static readonly StringNames CustomName = CustomStringName.CreateAndRegister("Custom"); + internal static readonly StringNames GamemodeName = MiraLocaleManager.GetOrCreateLocaleString("Gamemode"); + internal static readonly StringNames CustomName = MiraLocaleManager.GetOrCreateLocaleString("Custom"); internal static readonly Dictionary Values = new() { - [0] = CustomStringName.CreateAndRegister("Classic"), + [0] = MiraLocaleManager.GetOrCreateLocaleString("Classic"), }; internal static void AddOption(AbstractGameMode mode) { if (!Values.ContainsKey(mode.ID)) - Values.Add(mode.ID, CustomStringName.CreateAndRegister(mode.ColoredName)); + Values.Add(mode.ID, MiraLocaleManager.GetOrCreateLocaleString(mode + ".colored")); } /*[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))] [HarmonyPostfix] diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 30809bd..4e512af 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Networking; +using MiraAPI.Translation; using Reactor.Localization.Utilities; using UnityEngine; using Object = UnityEngine.Object; @@ -37,8 +38,8 @@ public class ModdedEnumOption : ModdedOption data.Title = StringName; data.Type = global::OptionTypes.String; data.Values = values is null ? - Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray() - : [.. values.Select(CustomStringName.CreateAndRegister)]; + Enum.GetNames(enumType).Select(MiraLocaleManager.GetOrCreateLocaleString).ToArray() + : [.. values.Select(MiraLocaleManager.GetOrCreateLocaleString)]; data.Index = Value; } @@ -136,8 +137,8 @@ public class ModdedEnumOption : ModdedOption where T : Enum data.Title = StringName; data.Type = global::OptionTypes.String; data.Values = values is null ? - Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray() - : [.. values.Select(CustomStringName.CreateAndRegister)]; + Enum.GetNames(typeof(T)).Select(MiraLocaleManager.GetOrCreateLocaleString).ToArray() + : [.. values.Select(MiraLocaleManager.GetOrCreateLocaleString)]; data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo); } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 6178eff..5fa0ceb 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -82,7 +82,7 @@ public abstract class ModdedOption : IModdedOption Title = title.Translate(); DefaultValue = defaultValue; Value = defaultValue; - StringName = CustomStringName.CreateAndRegister(Title.Translate()); + StringName = MiraLocaleManager.GetOrCreateLocaleString(Title); Visible = () => true; IncludeInPreset = includeInPreset; } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs index 62d07cf..630a9ec 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Networking; +using MiraAPI.Translation; using Reactor.Localization.Utilities; using UnityEngine; using Object = UnityEngine.Object; @@ -56,7 +57,7 @@ public class ModdedStringOption : ModdedOption newVals.Insert(index, value); Values = newVals.ToArray(); var newStrVals = StringNameValues.ToList(); - newStrVals.Insert(index, CustomStringName.CreateAndRegister(value)); + newStrVals.Insert(index, MiraLocaleManager.GetOrCreateLocaleString(value)); StringNameValues = newStrVals.ToArray(); } @@ -108,7 +109,7 @@ public class ModdedStringOption : ModdedOption data.Title = StringName; data.Type = global::OptionTypes.String; - StringNameValues = values.Select(CustomStringName.CreateAndRegister).ToArray(); + StringNameValues = values.Select(MiraLocaleManager.GetOrCreateLocaleString).ToArray(); data.Values = StringNameValues; data.Index = GetIndex(Value, 3); diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs index 3a1e507..3cb02e3 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs @@ -47,7 +47,7 @@ public class LocalToggleSetting : LocalSettingBase toggleObject.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7); } - toggleObject.BaseText = CustomStringName.CreateAndRegister(Name.Translate()); + toggleObject.BaseText = MiraLocaleManager.GetOrCreateLocaleString(Name); toggleObject.UpdateText(GetValue()); toggleObject.name = Name.Translate(); toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor; diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index bfaf1c4..dec15b6 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -457,7 +457,7 @@ internal static class GameOptionsMenuPatch num -= 0.58f; - categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); + categoryHeaderMasked.SetHeader(MiraLocaleManager.GetOrCreateLocaleString(group.GroupName), 20); categoryHeaderMasked.Background.color = group.GroupColor; categoryHeaderMasked.Divider.color = group.GroupColor; categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) @@ -697,7 +697,7 @@ internal static class GameOptionsMenuPatch Quaternion.identity, container); - categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName.Translate()), 20); + categoryHeaderMasked.SetHeader(MiraLocaleManager.GetOrCreateLocaleString(group.GroupName), 20); categoryHeaderMasked.Background.color = group.GroupColor; categoryHeaderMasked.Divider.color = group.GroupColor; categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 6ee00cd..0a5acee 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -32,8 +32,7 @@ public static class LobbyViewPanePatches : Plugins[SelectedModIdx - 1]; private static PassiveButton? ModifiersTabButton { get; set; } - - private static StringNames ModifiersTabName { get; } = CustomStringName.CreateAndRegister("ModifiersTab"); + internal static StringNames ModifiersTabName { get; } = MiraLocaleManager.GetOrCreateLocaleString("ModifiersTab"); private static AbstractGameMode? GetCustomGamemode() { @@ -455,7 +454,7 @@ public static class LobbyViewPanePatches // sort the groups by priority var sortedRoleGroups = roleGroups .OrderBy(x => x.Key.Priority) - .ThenBy(x => x.Key.Name); + .ThenBy(x => x.Key.Name.Translate()); foreach (var grouping in sortedRoleGroups) { @@ -466,13 +465,7 @@ public static class LobbyViewPanePatches var group = grouping.Key; - var translatedName = group.Name.Translate(); - var name = translatedName switch - { - "Crewmate" => StringNames.CrewmateRolesHeader, - "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(translatedName), - }; + var name = MiraLocaleManager.GetOrCreateLocaleString(group.Name); var categoryHeaderRoleVariant = Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); categoryHeaderRoleVariant.SetHeader(name, 61); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index d08961e..ac444da 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -196,7 +196,7 @@ public static class RoleSettingMenuPatches // sort the groups by priority var sortedRoleGroups = roleGroups .OrderBy(x => x.Key.Priority) - .ThenBy(x => x.Key.Name); + .ThenBy(x => x.Key.Name.Translate()); var quotaThing = roleMenu.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); var usingNewQuota = false; @@ -215,13 +215,7 @@ public static class RoleSettingMenuPatches RoleGroupHidden.TryAdd(group, false); - var translatedName = group.Name.Translate(); - var name = translatedName switch - { - "Crewmate" => StringNames.CrewmateRolesHeader, - "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(translatedName), - }; + var name = MiraLocaleManager.GetOrCreateLocaleString(group.Name); var categoryHeaderMasked = Object.Instantiate( template, diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index 7f3a2b0..5ee4b83 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -29,10 +29,14 @@ Outlive the timer or eliminate the crew. Save Roles Modifiers - Crewmate + Crewmate Crew - Impostor + Crewmate Roles + Impostor Imp - Neutral + Impostor Roles + Neutral Neut + Neutral Roles + Other diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index a54c439..0f4e839 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -132,9 +132,10 @@ public static class CustomRoleManager roleBehaviour.Role = (RoleTypes)roleId; roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Custom ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; - roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName.Translate()); - roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription.Translate()); - roleBehaviour.BlurbNameLong = CustomStringName.CreateAndRegister(customRole.RoleLongDescription.Translate()); + roleBehaviour.StringName = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleName); + roleBehaviour.BlurbName = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleDescription); + roleBehaviour.BlurbNameMed = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleMedDescription); + roleBehaviour.BlurbNameLong = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleLongDescription); roleBehaviour.AffectedByLightAffectors = customRole.Configuration.AffectedByLightOnAirship; roleBehaviour.CanBeKilled = customRole.Configuration.CanGetKilled; roleBehaviour.CanUseKillButton = customRole.Configuration.UseVanillaKillButton; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 8e233b8..77d4ce8 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text; using BepInEx.Configuration; using MiraAPI.GameOptions; @@ -17,7 +18,7 @@ public interface ICustomRole : IOptionable /// /// Gets the id part used to build the role's translation keys. /// - string IdPart => GetType().Name.ToLower(); + string IdPart => GetType().Name.ToLower(CultureInfo.InvariantCulture); /// /// Gets the name of the role. @@ -44,10 +45,10 @@ public interface ICustomRole : IOptionable /// string RoleFactionTitle => Team switch { - ModdedRoleTeams.Crewmate => TranslationController.Instance.GetString(StringNames.Crewmate), - ModdedRoleTeams.Impostor => TranslationController.Instance.GetString(StringNames.Impostor), - ModdedRoleTeams.Custom => "Neutral", - _ => "Other", + ModdedRoleTeams.Crewmate => MiraLocaleManager.Get("roleTeam.crewmate"), + ModdedRoleTeams.Impostor => MiraLocaleManager.Get("roleTeam.impostor"), + ModdedRoleTeams.Custom => MiraLocaleManager.Get("roleTeam.neutral"), + _ => MiraLocaleManager.Get("roleTeam.other"), }; /// @@ -78,7 +79,7 @@ public interface ICustomRole : IOptionable ModdedRoleTeams.Crewmate => RoleOptionsGroup.Crewmate, ModdedRoleTeams.Impostor => RoleOptionsGroup.Impostor, ModdedRoleTeams.Custom => RoleOptionsGroup.Neutral, - _ => new RoleOptionsGroup(RoleName.Translate(), RoleColor), + _ => new RoleOptionsGroup(RoleName, RoleColor), }; /// diff --git a/MiraAPI/Roles/RoleOptionsGroup.cs b/MiraAPI/Roles/RoleOptionsGroup.cs index 46b6067..a53ea95 100644 --- a/MiraAPI/Roles/RoleOptionsGroup.cs +++ b/MiraAPI/Roles/RoleOptionsGroup.cs @@ -13,15 +13,15 @@ public record struct RoleOptionsGroup(string Name, Color Color, int Priority = 0 /// /// Gets the default group for crewmates. /// - public static RoleOptionsGroup Crewmate { get; } = new("Crewmate", Palette.CrewmateBlue, -2); + public static RoleOptionsGroup Crewmate { get; } = new("roleTeam.crewmate.group", Palette.CrewmateBlue, -2); /// /// Gets the default group for impostors. /// - public static RoleOptionsGroup Impostor { get; } = new("Impostor", Palette.ImpostorRed, -1); + public static RoleOptionsGroup Impostor { get; } = new("roleTeam.impostor.group", Palette.ImpostorRed, -1); /// /// Gets the default group for neutral roles. /// - public static RoleOptionsGroup Neutral { get; } = new("Neutral Roles", Color.gray); + public static RoleOptionsGroup Neutral { get; } = new("roleTeam.neutral.group", Color.gray); } diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs index ee6b1d8..9f9bfd7 100644 --- a/MiraAPI/Translation/MiraLocaleManager.cs +++ b/MiraAPI/Translation/MiraLocaleManager.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Xml; +using Reactor.Localization.Utilities; using UnityEngine; namespace MiraAPI.Translation; @@ -12,7 +13,10 @@ public static class MiraLocaleManager { private const string LangDirectory = "mira_languages"; - private static readonly Dictionary>> Locale = []; + public static readonly Dictionary>> Locale = []; + + public static readonly Dictionary RegisteredStringNames = []; + internal static readonly Dictionary StringNamesLookup = []; public static Dictionary LangList { get; } = new() { @@ -177,6 +181,19 @@ public static class MiraLocaleManager return Path.Combine(Application.persistentDataPath, LangDirectory, modGuid); } + public static StringNames GetOrCreateLocaleString(string name) + { + if (RegisteredStringNames.TryGetValue(name, out var stringName)) + { + return stringName; + } + + var newString = CustomStringName.CreateAndRegister(name); + RegisteredStringNames.Add(name, newString); + StringNamesLookup.Add(newString, name); + return newString; + } + private static void LoadInternalStrings(Assembly assembly, string modGuid, string dir) { Directory.CreateDirectory(dir); diff --git a/MiraAPI/Translation/MiraLocalizationProvider.cs b/MiraAPI/Translation/MiraLocalizationProvider.cs index 20b80ed..7505855 100644 --- a/MiraAPI/Translation/MiraLocalizationProvider.cs +++ b/MiraAPI/Translation/MiraLocalizationProvider.cs @@ -13,19 +13,10 @@ public class MiraLocalizationProvider : LocalizationProvider public override bool TryGetText(StringNames stringName, out string? result) { - if (_reactorProvider != null && (int)stringName < 0 && _reactorProvider.TryGetText(stringName, out var reactorText)) + if (MiraLocaleManager.StringNamesLookup.TryGetValue(stringName, out var key)) { - if (reactorText.IsNullOrWhiteSpace()) - { - result = "STRMISS"; - return true; - } - var localeText = MiraLocaleManager.Get(reactorText!); - if (localeText != reactorText) - { - result = localeText; - return true; - } + result = MiraLocaleManager.Get(key); + return true; } result = null; return false; From 276ab0ac79e9f4eb47b764b052cd472c9156d421 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:02:55 -0500 Subject: [PATCH 11/16] Fix overall localization --- MiraAPI.Example/Resources/Locale/en_US.xml | 61 ++++++++++--------- MiraAPI.Example/Roles/ChameleonRole.cs | 2 +- MiraAPI.Example/Roles/FreezerRole.cs | 2 +- MiraAPI.Example/Roles/MayorRole.cs | 2 +- MiraAPI.Example/Roles/NeutralKillerRole.cs | 2 +- MiraAPI.Example/Roles/TeleporterRole.cs | 2 +- .../Patches/Options/RoleSettingMenuPatches.cs | 2 +- MiraAPI/PluginLoading/IMiraPlugin.cs | 12 ++-- MiraAPI/Roles/CustomRoleManager.cs | 14 ++--- MiraAPI/Roles/CustomRoleUtils.cs | 4 +- MiraAPI/Roles/ICustomRole.cs | 53 ++++++++++++---- MiraAPI/Utilities/Helpers.cs | 2 +- 12 files changed, 97 insertions(+), 61 deletions(-) diff --git a/MiraAPI.Example/Resources/Locale/en_US.xml b/MiraAPI.Example/Resources/Locale/en_US.xml index fd429f8..4039ad9 100644 --- a/MiraAPI.Example/Resources/Locale/en_US.xml +++ b/MiraAPI.Example/Resources/Locale/en_US.xml @@ -1,57 +1,60 @@ - Teleporter - Zoom out and teleport across the map! - Freezer - Freeze another player for a duration of time. - Mayor - You get extra votes! - Chamelon - Stay invisible while not moving. - Outcast Killer - Outcast who can kill. - Outcast + Teleporter + Zoom out and teleport across the map! + Zoom out and teleport across the map! + Zoom out and teleport across the map! + The Teleporter is a Crewmate role that can move around the map using their dope-ass ability. + Freezer + Freeze another player for a duration of time. + Mayor + You get extra votes! + Chamelon + Stay invisible while not moving. + Outcast Killer + Outcast who can kill. + Outcast OUTCAST You are an Outcast. You do not have a team. - Think + Think Thinking... - Freeze - Teleport - Call Meeting - Win Game - Captain - You can call a meeting from anywhere on the map. - Freezed - High Priority - You are high priority! Idrk what this means either tbh... - Example Options 1 + Freeze + Teleport + Call Meeting + Win Game + Captain + You can call a meeting from anywhere on the map. + Freezed + High Priority + You are high priority! Idrk what this means either tbh... + Example Options 1 Toggle Opt 1 Toggle Opt 2 Number Opt Best API - Example Options 2 + Example Options 2 YOU MUST CHOSE A PLAYER Eh whatever Toggle Option 1 Toggle Option 2 Enum Opt - Captain Settings + Captain Settings Amount Chance Uses - Freeze + Freeze Use Color - General + General Some Boolean Option Number Option - Custom Role + Custom Role Freeze Duration Freeze Uses - Teleporter + Teleporter Teleport Cooldown Teleport Duration Zoom Distance - Mira Example + Mira Example Example Bool Example Bool 2 Slider diff --git a/MiraAPI.Example/Roles/ChameleonRole.cs b/MiraAPI.Example/Roles/ChameleonRole.cs index d396fe6..e3f91b7 100644 --- a/MiraAPI.Example/Roles/ChameleonRole.cs +++ b/MiraAPI.Example/Roles/ChameleonRole.cs @@ -10,7 +10,7 @@ namespace MiraAPI.Example.Roles; public class ChameloenRole : CrewmateRole, ICustomRole { - public string IdPart => "chamelon"; + public string IdPart => "ApiExample.Role.Chamelon"; public Color RoleColor => Palette.AcceptedGreen; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs index 33656c0..d365b37 100644 --- a/MiraAPI.Example/Roles/FreezerRole.cs +++ b/MiraAPI.Example/Roles/FreezerRole.cs @@ -6,7 +6,7 @@ namespace MiraAPI.Example.Roles; public class FreezerRole : ImpostorRole, ICustomRole { - public string IdPart => "freezer"; + public string IdPart => "ApiExample.Role.Freezer"; public Color RoleColor => Palette.Blue; public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; diff --git a/MiraAPI.Example/Roles/MayorRole.cs b/MiraAPI.Example/Roles/MayorRole.cs index b06d350..6dae223 100644 --- a/MiraAPI.Example/Roles/MayorRole.cs +++ b/MiraAPI.Example/Roles/MayorRole.cs @@ -5,7 +5,7 @@ namespace MiraAPI.Example.Roles; public class MayorRole : CrewmateRole, ICustomRole { - public string IdPart => "mayor"; + public string IdPart => "ApiExample.Role.Mayor"; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public CustomRoleConfiguration Configuration => new(this); diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index 4202cd1..e952703 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -8,7 +8,7 @@ namespace MiraAPI.Example.Roles; public class NeutralKillerRole : ImpostorRole, ICustomRole { - public string IdPart => "outcastKiller"; + public string IdPart => "ApiExample.Role.OutcastKiller"; public Color RoleColor => Color.magenta; public ModdedRoleTeams Team => ModdedRoleTeams.Custom; diff --git a/MiraAPI.Example/Roles/TeleporterRole.cs b/MiraAPI.Example/Roles/TeleporterRole.cs index 7136ff0..8c114b8 100644 --- a/MiraAPI.Example/Roles/TeleporterRole.cs +++ b/MiraAPI.Example/Roles/TeleporterRole.cs @@ -5,7 +5,7 @@ namespace MiraAPI.Example.Roles; public class TeleporterRole : CrewmateRole, ICustomRole { - public string IdPart => "teleporter"; + public string IdPart => "ApiExample.Role.Teleporter"; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index ac444da..ff31dfa 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -575,7 +575,7 @@ public static class RoleSettingMenuPatches return; } - __instance.roleDescriptionText.text = customRole.RoleLongDescription.Translate(); + __instance.roleDescriptionText.text = customRole.RoleMedDescription; __instance.roleTitleText.text = role.GetRoleName(); var imgBg = __instance.AdvancedRolesSettings.transform.FindChild("Imagebackground"); diff --git a/MiraAPI/PluginLoading/IMiraPlugin.cs b/MiraAPI/PluginLoading/IMiraPlugin.cs index 6fb13ed..21cd9d9 100644 --- a/MiraAPI/PluginLoading/IMiraPlugin.cs +++ b/MiraAPI/PluginLoading/IMiraPlugin.cs @@ -1,7 +1,6 @@ -using System.Linq; +using System.Globalization; +using System.Linq; using BepInEx.Configuration; -using MiraAPI.Utilities.Assets; -using UnityEngine; namespace MiraAPI.PluginLoading; @@ -15,11 +14,16 @@ public interface IMiraPlugin /// public virtual bool DisplayOnOptionsMenu => true; + /// + /// Gets or sets a value for the localization id to use for anything registered under this mod. + /// + public virtual string ParentLocaleId => GetType().Name.ToLower(CultureInfo.InvariantCulture); + /// /// Gets the name to display on the options menu. /// string OptionsTitleText { get; } - + /// /// Gets the abbreviated name to display for other mods to pick up. /// diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 0f4e839..bc93252 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -132,10 +132,10 @@ public static class CustomRoleManager roleBehaviour.Role = (RoleTypes)roleId; roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Custom ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; - roleBehaviour.StringName = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleName); - roleBehaviour.BlurbName = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleDescription); - roleBehaviour.BlurbNameMed = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleMedDescription); - roleBehaviour.BlurbNameLong = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleLongDescription); + roleBehaviour.StringName = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleNameLocale); + roleBehaviour.BlurbName = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleDescriptionLocale); + roleBehaviour.BlurbNameMed = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleMedDescriptionLocale); + roleBehaviour.BlurbNameLong = MiraLocaleManager.GetOrCreateLocaleString(customRole.RoleLongDescriptionLocale); roleBehaviour.AffectedByLightAffectors = customRole.Configuration.AffectedByLightOnAirship; roleBehaviour.CanBeKilled = customRole.Configuration.CanGetKilled; roleBehaviour.CanUseKillButton = customRole.Configuration.UseVanillaKillButton; @@ -187,7 +187,7 @@ public static class CustomRoleManager if (useTaskHint && !overridesTaskText) { - Error($"Role {customRole.RoleName.Translate()} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!"); + Error($"Role {customRole.RoleName} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!"); } CustomRoles.Add(roleId, roleBehaviour); @@ -255,9 +255,9 @@ public static class CustomRoleManager { var tabText = panel.tab.gameObject.GetComponentInChildren(); var ogPanel = HudManager.Instance.TaskStuff.transform.FindChild("TaskPanel").gameObject.GetComponent(); - if (tabText.text != role.RoleName.Translate()) + if (tabText.text != role.RoleName) { - tabText.text = role.RoleName.Translate(); + tabText.text = role.RoleName; } var y = ogPanel.taskText.textBounds.size.y + 1; diff --git a/MiraAPI/Roles/CustomRoleUtils.cs b/MiraAPI/Roles/CustomRoleUtils.cs index c418a30..d43acbe 100644 --- a/MiraAPI/Roles/CustomRoleUtils.cs +++ b/MiraAPI/Roles/CustomRoleUtils.cs @@ -104,9 +104,9 @@ public static class CustomRoleUtils public static StringBuilder CreateForRole(ICustomRole role) { var taskStringBuilder = new StringBuilder(); - taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is {role.RoleName.Translate()}."); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is {role.RoleName}."); taskStringBuilder.Append(""); - taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription.Translate()}"); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}"); return taskStringBuilder; } diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 77d4ce8..c214619 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text; using BepInEx.Configuration; using MiraAPI.GameOptions; @@ -18,27 +17,57 @@ public interface ICustomRole : IOptionable /// /// Gets the id part used to build the role's translation keys. /// - string IdPart => GetType().Name.ToLower(CultureInfo.InvariantCulture); + string IdPart => GetType().Name; /// /// Gets the name of the role. /// - string RoleName => MiraLocaleManager.BuildTranslationId(IdPart, "name"); + string RoleName => MiraLocaleManager.Get(RoleNameLocale); + + /// + /// Gets the role's name id for localization. + /// + string RoleNameLocale => IdPart; /// /// Gets the description of the role. Used in the Intro Cutscene. /// - string RoleDescription => MiraLocaleManager.BuildTranslationId(IdPart, "description"); + string RoleDescription => MiraLocaleManager.Get(RoleDescriptionLocale); /// - /// Gets the medium description of the role. Used in the role guide. + /// Gets the role's intro blurb id for localization. /// - string RoleMedDescription => RoleDescription; + string RoleDescriptionLocale => MiraLocaleManager.BuildTranslationId(IdPart, "IntroBlurb"); /// - /// Gets the long description of the role. Used in the Role Tab and Role Options. + /// Gets the medium description of the role. Used in the role guide and options menu. /// - string RoleLongDescription => MiraLocaleManager.BuildTranslationId(IdPart, "description"); + string RoleMedDescription => MiraLocaleManager.Get(RoleMedDescriptionLocale); + + /// + /// Gets the role's medium description id for localization. + /// + string RoleMedDescriptionLocale => MiraLocaleManager.BuildTranslationId(IdPart, "MedDescription"); + + /// + /// Gets the long description of the role. Used in the Role Tab. + /// + string RoleLongDescription => MiraLocaleManager.Get(RoleLongDescriptionLocale); + + /// + /// Gets the role's long description id for localization. + /// + string RoleLongDescriptionLocale => MiraLocaleManager.BuildTranslationId(IdPart, "TabDescription"); + + /// + /// Gets the wiki description of the role. Used in the wiki, but not currently required. + /// + string RoleWikiDescription => MiraLocaleManager.Get(RoleLongDescriptionLocale); + + /// + /// Gets the role's wiki description id for localization. + /// + string RoleWikiDescriptionLocale => MiraLocaleManager.BuildTranslationId(IdPart, "WikiDescription"); /// /// Gets the faction / alignment of the role. Used in the role guide. @@ -187,7 +216,7 @@ public interface ICustomRole : IOptionable { if (!Configuration.CanModifyChance) { - Error($"Cannot modify chance for role: {RoleName.Translate()}"); + Error($"Cannot modify chance for role: {RoleName}"); return; } @@ -197,7 +226,7 @@ public interface ICustomRole : IOptionable return; } - Error($"Error getting chance configuration for role: {RoleName.Translate()}"); + Error($"Error getting chance configuration for role: {RoleName}"); } /// @@ -212,7 +241,7 @@ public interface ICustomRole : IOptionable return; } - Error($"Error getting count configuration for role: {RoleName.Translate()}"); + Error($"Error getting count configuration for role: {RoleName}"); } /// @@ -252,7 +281,7 @@ public interface ICustomRole : IOptionable /// A string with a custom ejection message or . string? GetCustomEjectionMessage(NetworkedPlayerInfo player) { - return Team == ModdedRoleTeams.Impostor ? $"{player.PlayerName} was The {RoleName.Translate()}" : null; + return Team == ModdedRoleTeams.Impostor ? $"{player.PlayerName} was The {RoleName}" : null; } /// diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 54f1674..687298e 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -537,7 +537,7 @@ public static class Helpers { if (role is ICustomRole custom) { - return custom.RoleName.Translate(); + return custom.RoleName; } return role.NiceName; } From 4206b74d1206e323ba180a5102e05694169dd776 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 26 Aug 2026 00:24:02 -0500 Subject: [PATCH 12/16] Fix localization being applied --- .../Buttons/Freezer/FreezeButton.cs | 2 +- MiraAPI.Example/Buttons/MeetingButton.cs | 2 +- .../Buttons/NeutralKillerButton.cs | 2 +- .../Buttons/Teleporter/TeleportButton.cs | 2 +- MiraAPI.Example/Buttons/ThinkButton.cs | 4 +- MiraAPI.Example/ExampleLocalSetings.cs | 2 +- MiraAPI.Example/Modifiers/CaptainModifier.cs | 2 +- .../Modifiers/Freezer/FreezeModifier.cs | 2 +- .../Modifiers/HighPriorityModifier.cs | 2 +- MiraAPI.Example/Options/ExampleOptions.cs | 2 +- MiraAPI.Example/Options/ExampleOptions2.cs | 2 +- .../Modifiers/CaptainModifierSettings.cs | 2 +- .../Modifiers/FreezeModifierSettings.cs | 2 +- .../Modifiers/GeneralModifierOptions.cs | 2 +- .../Options/Roles/FreezerRoleSettings.cs | 6 +- .../Options/Roles/TeleporterOptions.cs | 8 +- MiraAPI.Example/Resources/Locale/en_US.xml | 139 ++++++++++-------- MiraAPI.Example/Resources/Locale/zh_CN.xml | 60 ++++---- MiraAPI.Example/Roles/NeutralKillerRole.cs | 2 +- MiraAPI/GameModes/AbstractGameMode.cs | 6 +- MiraAPI/GameModes/GameModeOption.cs | 4 +- MiraAPI/MiraApiSettings.cs | 40 ++--- MiraAPI/Patches/GameModes/HudPatches.cs | 3 +- .../Patches/Options/LobbyViewPanePatches.cs | 2 +- MiraAPI/Resources/Locale/en_US.xml | 4 +- MiraAPI/Resources/Locale/zh_CN.xml | 4 +- 26 files changed, 163 insertions(+), 145 deletions(-) diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 8d02a29..34fce19 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -13,7 +13,7 @@ namespace MiraAPI.Example.Buttons.Freezer; public class FreezeButton : CustomActionButton { - public override string Name => "button.freeze.name"; + public override string Name => "button.freeze"; public override float Cooldown => OptionGroupSingleton.Instance.FreezeDuration; public override bool PauseTimerInVent => true; diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs index 9949ae2..d43bda6 100644 --- a/MiraAPI.Example/Buttons/MeetingButton.cs +++ b/MiraAPI.Example/Buttons/MeetingButton.cs @@ -9,7 +9,7 @@ namespace MiraAPI.Example.Buttons; public class MeetingButton : CustomActionButton { - public override string Name => "button.callMeeting.name"; + public override string Name => "button.callMeeting"; public override float Cooldown => 15; diff --git a/MiraAPI.Example/Buttons/NeutralKillerButton.cs b/MiraAPI.Example/Buttons/NeutralKillerButton.cs index 0d40183..3771c58 100644 --- a/MiraAPI.Example/Buttons/NeutralKillerButton.cs +++ b/MiraAPI.Example/Buttons/NeutralKillerButton.cs @@ -10,7 +10,7 @@ namespace MiraAPI.Example.Buttons; public class NeutralKillerButton : CustomActionButton { - public override string Name => "button.winGame.name"; + public override string Name => "button.winGame"; public override float Cooldown => 0f; public override LoadableAsset Sprite => ExampleAssets.ExampleButton; public override MiraKeybind Keybind => ExampleKeybinds.NeutralWinKeybind; diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs index 681a7dc..8dead81 100644 --- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs +++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs @@ -12,7 +12,7 @@ namespace MiraAPI.Example.Buttons.Teleporter; public class TeleportButton : CustomActionButton { - public override string Name => "button.teleport.name"; + public override string Name => "button.teleport"; public override float Cooldown => OptionGroupSingleton.Instance.TeleportCooldown.Value; diff --git a/MiraAPI.Example/Buttons/ThinkButton.cs b/MiraAPI.Example/Buttons/ThinkButton.cs index 8cc0c63..71013e9 100644 --- a/MiraAPI.Example/Buttons/ThinkButton.cs +++ b/MiraAPI.Example/Buttons/ThinkButton.cs @@ -7,7 +7,7 @@ namespace MiraAPI.Example.Buttons; public class ThinkButton : CustomActionButton { - public override string Name => "button.think.name"; + public override string Name => "button.think"; public override float Cooldown => 15f; public override float EffectDuration => 10f; public override int MaxUses => 1; @@ -20,7 +20,7 @@ public class ThinkButton : CustomActionButton public override void OnEffectEnd() { - Button!.OverrideText("button.think.name".Translate()); + Button!.OverrideText("button.think".Translate()); } public override bool IsEffectCancellable() diff --git a/MiraAPI.Example/ExampleLocalSetings.cs b/MiraAPI.Example/ExampleLocalSetings.cs index 5825914..319e2cf 100644 --- a/MiraAPI.Example/ExampleLocalSetings.cs +++ b/MiraAPI.Example/ExampleLocalSetings.cs @@ -8,7 +8,7 @@ namespace MiraAPI.Example; public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config) { - public override string TabName => "settings.miraExample.name"; + public override string TabName => "settings.miraExample"; protected override bool ShouldCreateLabels => false; public override LocalSettingTabAppearance TabAppearance => new() diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 4cf16b5..20e3872 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -10,7 +10,7 @@ namespace MiraAPI.Example.Modifiers; public class CaptainModifier : GameModifier { - public override string ModifierName => "modifier.captain.name"; + public override string ModifierName => "modifier.captain"; public override LoadableAsset? ModifierIcon => ExampleAssets.CallMeetingButton; public override void OnDeath(DeathReason reason) diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs index 678707f..8cc961b 100644 --- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs +++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs @@ -7,7 +7,7 @@ namespace MiraAPI.Example.Modifiers.Freezer; public class FreezeModifier : TimedModifier { - public override string ModifierName => "modifier.freezed.name"; + public override string ModifierName => "modifier.freezed"; public override bool HideOnUi => false; public override float Duration => 15f; public override bool AutoStart => true; diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index 3d7c444..a637df5 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -4,7 +4,7 @@ namespace MiraAPI.Example.Modifiers; public class HighPriorityModifier : GameModifier { - public override string ModifierName => "modifier.highPriority.name"; + public override string ModifierName => "modifier.highPriority"; public override string GetDescription() { diff --git a/MiraAPI.Example/Options/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs index 055bdb3..aff6ccf 100644 --- a/MiraAPI.Example/Options/ExampleOptions.cs +++ b/MiraAPI.Example/Options/ExampleOptions.cs @@ -7,7 +7,7 @@ namespace MiraAPI.Example.Options; public class ExampleOptions : AbstractOptionGroup { - public override string GroupName => "options.exampleOptions1.name"; + public override string GroupName => "options.exampleOptions1"; public override Color GroupColor => Color.green; [ModdedToggleOption("options.exampleOptions1.toggleOpt1")] diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs index 92f0c15..6acba04 100644 --- a/MiraAPI.Example/Options/ExampleOptions2.cs +++ b/MiraAPI.Example/Options/ExampleOptions2.cs @@ -6,7 +6,7 @@ namespace MiraAPI.Example.Options; public class ExampleOptions2 : AbstractOptionGroup { - public override string GroupName => "options.exampleOptions2.name"; + public override string GroupName => "options.exampleOptions2"; public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue. diff --git a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs index 73ef735..e850385 100644 --- a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs +++ b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs @@ -6,7 +6,7 @@ namespace MiraAPI.Example.Options.Modifiers; public class CaptainModifierSettings : AbstractOptionGroup { - public override string GroupName => "options.captainSettings.name"; + public override string GroupName => "options.captainSettings"; [ModdedNumberOption("options.captainSettings.amount", 0, 5)] public float Amount { get; set; } = 1; diff --git a/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs index 8cd0117..f6296ba 100644 --- a/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs +++ b/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs @@ -6,7 +6,7 @@ namespace MiraAPI.Example.Options.Modifiers; public class FreezeModifierSettings : AbstractOptionGroup { - public override string GroupName => "options.freezeModifierSettings.name"; + public override string GroupName => "options.freezeModifierSettings"; [ModdedToggleOption("options.freezeModifierSettings.useColor")] public bool UseColor { get; set; } = true; diff --git a/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs index 6744d5d..8d0f82d 100644 --- a/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs +++ b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs @@ -5,7 +5,7 @@ namespace MiraAPI.Example.Options.Modifiers; public class GeneralModifierOptions : AbstractOptionGroup { - public override string GroupName => "options.generalModifier.name"; + public override string GroupName => "options.generalModifier"; public override uint GroupPriority => 0; diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index f42edf5..b87b84e 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -13,11 +13,11 @@ public class FreezerRoleSettings : AbstractOptionGroup TmpSpriteUtils.CreateSpriteAsset( MiraAssets.ImpostorFile.LoadAsset(), "ApiExample.Role.Impostor.FreezerRole")); - public override string GroupName => "options.freezerRoleSettings.name"; + public override string GroupName => "ApiExample.Role.Freezer"; - [ModdedNumberOption("options.freezerRoleSettings.freezeDuration", 1, 15, 1, MiraNumberSuffixes.Seconds)] + [ModdedNumberOption("ApiExample.Role.Freezer.Settings.FreezeDuration", 1, 15, 1, MiraNumberSuffixes.Seconds)] public float FreezeDuration { get; set; } = 5; - [ModdedNumberOption("options.freezerRoleSettings.freezeUses", 1, 5)] + [ModdedNumberOption("ApiExample.Role.Freezer.Settings.FreezeUses", 1, 5)] public float FreezeUses { get; set; } = 1; } diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs index 2f8450e..4a4f694 100644 --- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs +++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs @@ -8,13 +8,13 @@ namespace MiraAPI.Example.Options.Roles; public class TeleporterOptions : AbstractRoleOptionGroup { - public override string GroupName => "options.teleporterOptions.name"; + public override string GroupName => "ApiExample.Role.Teleporter"; - public ModdedNumberOption TeleportCooldown { get; set; } = new("options.teleporterOptions.teleportCooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds); + public ModdedNumberOption TeleportCooldown { get; set; } = new("ApiExample.Role.Teleporter.Settings.TeleportCooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds); - [ModdedNumberOption("options.teleporterOptions.teleportDuration", 5, 25, 1, MiraNumberSuffixes.Seconds)] + [ModdedNumberOption("ApiExample.Role.Teleporter.Settings.TeleportDuration", 5, 25, 1, MiraNumberSuffixes.Seconds)] public float TeleportDuration { get; set; } = 10; - [ModdedNumberOption("options.teleporterOptions.zoomDistance", 4, 15)] + [ModdedNumberOption("ApiExample.Role.Teleporter.Settings.ZoomDistance", 4, 15)] public float ZoomDistance { get; set; } = 6; } diff --git a/MiraAPI.Example/Resources/Locale/en_US.xml b/MiraAPI.Example/Resources/Locale/en_US.xml index 4039ad9..5951728 100644 --- a/MiraAPI.Example/Resources/Locale/en_US.xml +++ b/MiraAPI.Example/Resources/Locale/en_US.xml @@ -1,69 +1,82 @@ - Teleporter + Teleporter Zoom out and teleport across the map! - Zoom out and teleport across the map! + Zoom out and teleport across the map! Zoom out and teleport across the map! The Teleporter is a Crewmate role that can move around the map using their dope-ass ability. - Freezer - Freeze another player for a duration of time. - Mayor - You get extra votes! - Chamelon - Stay invisible while not moving. - Outcast Killer - Outcast who can kill. - Outcast - OUTCAST - You are an Outcast. You do not have a team. - Think - Thinking... - Freeze - Teleport - Call Meeting - Win Game - Captain - You can call a meeting from anywhere on the map. - Freezed - High Priority - You are high priority! Idrk what this means either tbh... - Example Options 1 - Toggle Opt 1 - Toggle Opt 2 - Number Opt - Best API - Example Options 2 - YOU MUST CHOSE A PLAYER - Eh whatever - Toggle Option 1 - Toggle Option 2 - Enum Opt - Captain Settings - Amount - Chance - Uses - Freeze - Use Color - General - Some Boolean Option - Number Option - Custom Role - Freeze Duration - Freeze Uses - Teleporter - Teleport Cooldown - Teleport Duration - Zoom Distance - Mira Example - Example Bool - Example Bool 2 - Slider - Example Number - Example Multplier - Example Enum - Example Button - Outcast Killer Wins! - Cerulean - Rose - Gold + Freezer + Get iced, nerd. + Freeze others for a duration of time. + Freeze another player for a duration of time. + The Freezer is an Impostor role that can freeze players. Or something along those lines. + Mayor + Democracy is the best policy. + Vote out the Impostors with more votes. +Your votes have double the power. + You get one extra vote in the meeting! + The Mayor is a Crewmate role that gets two votes in the meeting, instead of the singular vote other Crewmates get. + Chamelon + Become one with the environment. + Fade out of everyone's vision by not moving. + Stay invisible while not moving. + The Chamelon is a Crewmate role that slowly becomes invisible while standing still. + Outcast Killer + Kill everyone else. + Be the last outcast standing. +Eliminate every other faction. + Outcast who can kill. + The Outcast Killer is an Outcast role that can kill, like some sort of neutral killer... + Outcast + OUTCAST + You are an Outcast. You do not have a team. + Think + Thinking... + Freeze + Teleport + Call Meeting + Win Game + Captain + You can call a meeting from anywhere on the map. + Freezed + High Priority + You are high priority! Idrk what this means either tbh... + Example Options 1 + Toggle Opt 1 + Toggle Opt 2 + Number Opt + Best API + Example Options 2 + YOU MUST CHOSE A PLAYER + Eh whatever + Toggle Option 1 + Toggle Option 2 + Enum Opt + Captain Settings + Amount + Chance + Uses + Freeze + Use Color + General + Some Boolean Option + Number Option + Custom Role + Freeze Duration + Freeze Uses + Teleport Cooldown + Teleport Duration + Zoom Distance + Mira Example + Example Bool + Example Bool 2 + Slider + Example Number + Example Multplier + Example Enum + Example Button + Outcast Killer Wins! + Cerulean + Rose + Gold diff --git a/MiraAPI.Example/Resources/Locale/zh_CN.xml b/MiraAPI.Example/Resources/Locale/zh_CN.xml index 3b5a53e..aa9f7eb 100644 --- a/MiraAPI.Example/Resources/Locale/zh_CN.xml +++ b/MiraAPI.Example/Resources/Locale/zh_CN.xml @@ -1,57 +1,59 @@ - 传送者 - 缩小视野并在地图上传送! - 冰冻者 - 将其他玩家冰冻一段时间。 - 市长 + 传送者 + 缩小视野并在地图上传送! + 缩小视野并在地图上传送! + 缩小视野并在地图上传送! + 冰冻者 + 将其他玩家冰冻一段时间。 + 市长 你拥有额外投票权! - 变色龙 + 变色龙 静止不动时保持隐身。 - 流放杀手 + 流放杀手 可以击杀的流放者。 - 流放者 + 流放者 流放者 你是流放者,你没有队伍。 - 思考 + 思考 思考中... - 冰冻 - 传送 - 召集会议 - 赢得游戏 - 队长 + 冰冻 + 传送 + 召集会议 + 赢得游戏 + 队长 你可以在地图上任意位置召集会议。 - 已被冰冻 - 高优先级 + 已被冰冻 + 高优先级 你是高优先级!老实说我也不太清楚这是什么意思…… - 示例选项组 1 + 示例选项组 1 开关选项 1 开关选项 2 数字选项 谁是最好的API - 示例选项组 2 + 示例选项组 2 你必须选择一个玩家 咕咕嘎嘎 开关选项 1 开关选项 2 枚举选项 - 队长设置 + 队长设置 数量 概率 使用次数 - 冰冻 + 冰冻 使用颜色 - 通用 + 通用 某个布尔选项 数字选项 - 自定义职业 - 冰冻时长 - 冰冻次数 - 传送者 - 传送冷却 - 传送持续时间 - 缩放距离 - Mira 示例插件 + 自定义职业 + 冰冻时长 + 冰冻次数 + 传送者 + 传送冷却 + 传送持续时间 + 缩放距离 + Mira 示例插件 示例布尔 示例布尔 2 滑块 diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index e952703..9ccec19 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -19,7 +19,7 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole CanUseVent = true, }; - public RoleOptionsGroup RoleOptionsGroup { get; } = new("outcast.name", Color.gray); + public RoleOptionsGroup RoleOptionsGroup { get; } = new("outcast", Color.gray); [HideFromIl2Cpp] public TeamIntroConfiguration? IntroConfiguration { get; } = new( diff --git a/MiraAPI/GameModes/AbstractGameMode.cs b/MiraAPI/GameModes/AbstractGameMode.cs index 37bbd39..10dd377 100644 --- a/MiraAPI/GameModes/AbstractGameMode.cs +++ b/MiraAPI/GameModes/AbstractGameMode.cs @@ -33,7 +33,7 @@ public abstract class AbstractGameMode : IOptionable public abstract string Description { get; } /// - /// Gets the game mode description. + /// Gets the game mode color to display on the lobby pane. /// public virtual Color Color { get; } = Color.white; @@ -43,14 +43,14 @@ public abstract class AbstractGameMode : IOptionable public virtual LoadableAsset? Icon => null; /// - /// Gets the icon of the gamemode for use on the options pop-up. + /// Gets or sets the icon of the gamemode for use on the options pop-up. /// public TMP_SpriteAsset TmpIcon { get; set; } = null!; /// /// Gets the colored game mode name, using the color and name properties. /// - public string ColoredName => $"{MiraLocaleManager.Get(Name)}"; + public string ColoredName => $"{MiraLocaleManager.Get(Name)}"; /// /// Gets the game mode id. diff --git a/MiraAPI/GameModes/GameModeOption.cs b/MiraAPI/GameModes/GameModeOption.cs index 180103d..792eabd 100644 --- a/MiraAPI/GameModes/GameModeOption.cs +++ b/MiraAPI/GameModes/GameModeOption.cs @@ -53,13 +53,13 @@ public static class GameModeOption internal static readonly StringNames CustomName = MiraLocaleManager.GetOrCreateLocaleString("Custom"); internal static readonly Dictionary Values = new() { - [0] = MiraLocaleManager.GetOrCreateLocaleString("Classic"), + [0] = MiraLocaleManager.GetOrCreateLocaleString("gamemode.classic"), }; internal static void AddOption(AbstractGameMode mode) { if (!Values.ContainsKey(mode.ID)) - Values.Add(mode.ID, MiraLocaleManager.GetOrCreateLocaleString(mode + ".colored")); + Values.Add(mode.ID, MiraLocaleManager.GetOrCreateLocaleString(mode.Name)); } /*[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))] [HarmonyPostfix] diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index 8259e7b..955be0e 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -17,7 +17,7 @@ namespace MiraAPI; public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) { /// - public override string TabName => "miraApi.name".Translate(); + public override string TabName => "miraApi"; /// public override LocalSettingTabAppearance TabAppearance => new() @@ -205,58 +205,58 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) } } - /// - /// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers. - /// - [LocalToggleSetting] - public ConfigEntry ModifiersHudLeftSide { get; private set; } = config.Bind("Visuals/UI", "Show Modifiers HUD on Left Side", false); - - /// - /// Gets whether to show keybinds on buttons. - /// - [LocalToggleSetting] - public ConfigEntry ShowKeybinds { get; private set; } = config.Bind("Visuals/UI", "Show Keybinds on Buttons", true); - /// /// Gets the scale of the ability buttons. /// [LocalSliderSetting(min: 0.5f, max: 1.5f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] public ConfigEntry ButtonUIFactorSlider { get; private set; } = - config.Bind("Visuals/UI", "Button Scale Factor", 0.75f); + config.Bind("miraApi.visualsUi", "miraApi.buttonScaleFactor", 0.75f); /// /// Gets the scale of the UI buttons. /// [LocalSliderSetting(min: 0.3f, max: 2f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] public ConfigEntry TopRightButtonsFactorSlider { get; private set; } = - config.Bind("Visuals/UI", "Top-Right UI Scale Factor", 1f); + config.Bind("miraApi.visualsUi", "miraApi.topRightButtonScaleFactor", 1f); /// /// Gets the fps specified by the player. /// [LocalSliderSetting(min: 60f, max: 240f, suffixType: MiraNumberSuffixes.None, formatString: "0", displayValue: true, roundValue: true)] public ConfigEntry SetFpsSlider { get; private set; } = - config.Bind("Visuals/UI", "Max FPS", 120f); + config.Bind("miraApi.visualsUi", "Max FPS", 120f); + + /// + /// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers. + /// + [LocalToggleSetting] + public ConfigEntry ModifiersHudLeftSide { get; private set; } = config.Bind("miraApi.visualsUi", "miraApi.showModifiersHudOnLeftSide", false); + + /// + /// Gets whether to show keybinds on buttons. + /// + [LocalToggleSetting] + public ConfigEntry ShowKeybinds { get; private set; } = config.Bind("miraApi.visualsUi", "miraApi.showKeybindsOnButtons", true); [LocalToggleSetting] public ConfigEntry WikiOnBottomRow { get; private set; } = - config.Bind("Visuals/UI", "Display Wiki on Bottom Row", true); + config.Bind("miraApi.visualsUi", "miraApi.wikiOnBottomRow", true); /// /// Gets whether to apply cosmetic changes to the TaskAdder. /// [LocalToggleSetting] - public ConfigEntry PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true); + public ConfigEntry PrettyTaskAdder { get; private set; } = config.Bind("miraApi.freeplay", "miraApi.prettyTaskLaptop", true); /// /// Gets whether to show the red flash from sabotages. /// [LocalToggleSetting] - public ConfigEntry EnableSabotageFlashes { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Flashes", true); + public ConfigEntry EnableSabotageFlashes { get; private set; } = config.Bind("miraApi.accessibility", "miraApi.enableSabotageFlashes", true); /// /// Gets whether to enable the sabotage sound effects or not. /// [LocalToggleSetting] - public ConfigEntry EnableSabotageBlares { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Blare", true); + public ConfigEntry EnableSabotageBlares { get; private set; } = config.Bind("miraApi.accessibility", "miraApi.enableSabotageBlare", true); } diff --git a/MiraAPI/Patches/GameModes/HudPatches.cs b/MiraAPI/Patches/GameModes/HudPatches.cs index 735d198..dba4731 100644 --- a/MiraAPI/Patches/GameModes/HudPatches.cs +++ b/MiraAPI/Patches/GameModes/HudPatches.cs @@ -2,6 +2,7 @@ using System.Collections; using HarmonyLib; using Il2CppSystem; using MiraAPI.GameModes; +using MiraAPI.Translation; using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -49,7 +50,7 @@ internal static class HudPatches gmText.SetActive(false); modelText.SetActive(false); _text = gmTextClone.GetComponent(); - _text.text = CustomGameModeManager.ActiveMode != null ? $"{CustomGameModeManager.ActiveMode.Name}" : "Classic"; + _text.text = CustomGameModeManager.ActiveMode != null ? CustomGameModeManager.ActiveMode.ColoredName : MiraLocaleManager.Get("gamemode.classic"); CustomGameModeManager.ActiveMode?.HudStart(instance); } diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 0a5acee..7c673d0 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -151,7 +151,7 @@ public static class LobbyViewPanePatches var cgm = GetCustomGamemode(); ModifiersTabButton?.gameObject.SetActive(false); menu.rolesTabButton.gameObject.SetActive(cgm is null or { ShowNormalRoleSettings: true }); - menu.gameModeText.text = cgm is null ? "Main" : cgm.ColoredName; + menu.gameModeText.text = cgm is null ? MiraLocaleManager.Get("gamemode.classic") : cgm.ColoredName; } else { diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index 5ee4b83..68c78c9 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -1,6 +1,6 @@ - Mira API + Mira API Visuals/UI Freeplay Accessibility @@ -8,7 +8,9 @@ Show Modifiers HUD on Left Side Show Keybinds on Buttons Button Scale Factor + Top-Right UI Scale Factor Max FPS + Display Wiki on Bottom Row Pretty Task Laptop Enable Sabotage Flashes Enable Sabotage Blare diff --git a/MiraAPI/Resources/Locale/zh_CN.xml b/MiraAPI/Resources/Locale/zh_CN.xml index 17df36e..89848c4 100644 --- a/MiraAPI/Resources/Locale/zh_CN.xml +++ b/MiraAPI/Resources/Locale/zh_CN.xml @@ -1,6 +1,6 @@ - Mira API + Mira API 界面/视觉 自由模式 无障碍 @@ -17,7 +17,7 @@ 自定义类别 2 在此配置附加职业及其设置! 为此模组应用游戏设置! - 默认 + 默认 默认原版游戏模式 点击关闭 为本游戏选择或创建一个预设 From 28a77f96cb2add2de61c1d3ccf457f315085a316 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 27 Aug 2026 18:21:14 -0500 Subject: [PATCH 13/16] Fix capitalization of all xml strings --- MiraAPI/GameModes/ClassicMode.cs | 4 +- MiraAPI/GameModes/GameModeOption.cs | 2 +- MiraAPI/GameModes/HideAndSeekMode.cs | 4 +- MiraAPI/MiraApiSettings.cs | 24 +++--- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 39 ++++------ MiraAPI/Patches/GameModes/HudPatches.cs | 2 +- MiraAPI/Patches/HudManagerPatches.cs | 16 ++-- .../Patches/Options/GameOptionsMenuPatch.cs | 6 +- .../Patches/Options/GamePresetsTabPatches.cs | 4 +- .../Patches/Options/LobbyViewPanePatches.cs | 4 +- MiraAPI/Patches/Options/MenuState.cs | 6 +- .../Patches/Options/RoleSettingMenuPatches.cs | 6 +- MiraAPI/Resources/Locale/en_US.xml | 76 +++++++++---------- MiraAPI/Resources/Locale/zh_CN.xml | 42 +++++----- MiraAPI/Roles/ICustomRole.cs | 8 +- MiraAPI/Roles/RoleOptionsGroup.cs | 6 +- 16 files changed, 117 insertions(+), 132 deletions(-) diff --git a/MiraAPI/GameModes/ClassicMode.cs b/MiraAPI/GameModes/ClassicMode.cs index 6a8ea1d..3da9a23 100644 --- a/MiraAPI/GameModes/ClassicMode.cs +++ b/MiraAPI/GameModes/ClassicMode.cs @@ -11,10 +11,10 @@ namespace MiraAPI.GameModes; public class ClassicMode : AbstractGameMode { /// - public override string Name => "gamemode.classic"; + public override string Name => "MiraApi.Gamemode.Classic"; /// - public override string Description => "gamemode.classic.description"; + public override string Description => "MiraApi.Gamemode.Classic.Description"; public override LoadableAsset? Icon => MiraAssets.ClassicGamemodeIcon; } diff --git a/MiraAPI/GameModes/GameModeOption.cs b/MiraAPI/GameModes/GameModeOption.cs index 792eabd..3e0d2f7 100644 --- a/MiraAPI/GameModes/GameModeOption.cs +++ b/MiraAPI/GameModes/GameModeOption.cs @@ -53,7 +53,7 @@ public static class GameModeOption internal static readonly StringNames CustomName = MiraLocaleManager.GetOrCreateLocaleString("Custom"); internal static readonly Dictionary Values = new() { - [0] = MiraLocaleManager.GetOrCreateLocaleString("gamemode.classic"), + [0] = MiraLocaleManager.GetOrCreateLocaleString("MiraApi.Gamemode.Classic"), }; internal static void AddOption(AbstractGameMode mode) diff --git a/MiraAPI/GameModes/HideAndSeekMode.cs b/MiraAPI/GameModes/HideAndSeekMode.cs index e30ab9b..cd99eae 100644 --- a/MiraAPI/GameModes/HideAndSeekMode.cs +++ b/MiraAPI/GameModes/HideAndSeekMode.cs @@ -25,10 +25,10 @@ namespace MiraAPI.GameModes; public class HideAndSeekMode : AbstractGameMode { /// - public override string Name => "gamemode.hidenseek"; + public override string Name => "MiraApi.Gamemode.HideAndSeek"; /// - public override string Description => "gamemode.hidenseek.description"; + public override string Description => "MiraApi.Gamemode.HideAndSeek.Description"; // TODO: Once the Hide n Seek port is ready for release, we will remove this. public override bool HideMode => !MiraApiPlugin.IsDevBuild; diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index 955be0e..9999f15 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -17,7 +17,7 @@ namespace MiraAPI; public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) { /// - public override string TabName => "miraApi"; + public override string TabName => "MiraApi"; /// public override LocalSettingTabAppearance TabAppearance => new() @@ -210,53 +210,47 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) /// [LocalSliderSetting(min: 0.5f, max: 1.5f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] public ConfigEntry ButtonUIFactorSlider { get; private set; } = - config.Bind("miraApi.visualsUi", "miraApi.buttonScaleFactor", 0.75f); + config.Bind("MiraApi.VisualsUi", "MiraApi.ButtonScaleFactor", 0.75f); /// /// Gets the scale of the UI buttons. /// [LocalSliderSetting(min: 0.3f, max: 2f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] public ConfigEntry TopRightButtonsFactorSlider { get; private set; } = - config.Bind("miraApi.visualsUi", "miraApi.topRightButtonScaleFactor", 1f); + config.Bind("MiraApi.VisualsUi", "MiraApi.TopRightButtonScaleFactor", 1f); /// /// Gets the fps specified by the player. /// [LocalSliderSetting(min: 60f, max: 240f, suffixType: MiraNumberSuffixes.None, formatString: "0", displayValue: true, roundValue: true)] public ConfigEntry SetFpsSlider { get; private set; } = - config.Bind("miraApi.visualsUi", "Max FPS", 120f); + config.Bind("MiraApi.VisualsUi", "Max FPS", 120f); /// /// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers. /// [LocalToggleSetting] - public ConfigEntry ModifiersHudLeftSide { get; private set; } = config.Bind("miraApi.visualsUi", "miraApi.showModifiersHudOnLeftSide", false); + public ConfigEntry ModifiersHudLeftSide { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.ShowModifiersHudOnLeftSide", false); /// /// Gets whether to show keybinds on buttons. /// [LocalToggleSetting] - public ConfigEntry ShowKeybinds { get; private set; } = config.Bind("miraApi.visualsUi", "miraApi.showKeybindsOnButtons", true); + public ConfigEntry ShowKeybinds { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.ShowKeybindsOnButtons", true); [LocalToggleSetting] public ConfigEntry WikiOnBottomRow { get; private set; } = - config.Bind("miraApi.visualsUi", "miraApi.wikiOnBottomRow", true); - - /// - /// Gets whether to apply cosmetic changes to the TaskAdder. - /// - [LocalToggleSetting] - public ConfigEntry PrettyTaskAdder { get; private set; } = config.Bind("miraApi.freeplay", "miraApi.prettyTaskLaptop", true); + config.Bind("MiraApi.VisualsUi", "MiraApi.WikiOnBottomRow", true); /// /// Gets whether to show the red flash from sabotages. /// [LocalToggleSetting] - public ConfigEntry EnableSabotageFlashes { get; private set; } = config.Bind("miraApi.accessibility", "miraApi.enableSabotageFlashes", true); + public ConfigEntry EnableSabotageFlashes { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableSabotageFlashes", true); /// /// Gets whether to enable the sabotage sound effects or not. /// [LocalToggleSetting] - public ConfigEntry EnableSabotageBlares { get; private set; } = config.Bind("miraApi.accessibility", "miraApi.enableSabotageBlare", true); + public ConfigEntry EnableSabotageBlares { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableSabotageBlare", true); } diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index f87c691..71fa411 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -5,7 +5,6 @@ using System.Text; using AmongUs.GameOptions; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; -using MiraAPI.LocalSettings; using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; @@ -269,13 +268,9 @@ public static class TaskAdderPatches stringBuilder.Append('\\'); } __instance.PathText.text = stringBuilder.ToString(); - var prettyEnabled = LocalSettingsTabSingleton.Instance.PrettyTaskAdder.Value; - if (prettyEnabled) - { - __instance.PathText.fontSizeMin = 3; - __instance.PathText.fontSizeMax = 3; - __instance.transform.FindChild("TitleText_TMP")?.gameObject.DestroyImmediate(); - } + __instance.PathText.fontSizeMin = 3; + __instance.PathText.fontSizeMax = 3; + __instance.transform.FindChild("TitleText_TMP")?.gameObject.DestroyImmediate(); __instance.ActiveItems.ToArray().Do(x => x.gameObject.DeepDestroy(false)); __instance.ActiveItems.Clear(); @@ -355,15 +350,12 @@ public static class TaskAdderPatches { TaskAddButton roleAddButton = Object.Instantiate(__instance.RoleButton); roleAddButton.SafePositionWorld = __instance.SafePositionWorld; - roleAddButton.Text.text = prettyEnabled ? role.GetRoleName() : "Be_" + role.GetRoleName() + ".exe"; + roleAddButton.Text.text = role.GetRoleName(); roleAddButton.Role = role; - if (prettyEnabled) - { - roleAddButton.Text.fontSizeMin = 1; - roleAddButton.FileImage.sprite = role.IsImpostor - ? MiraAssets.ImpostorFile.LoadAsset() - : MiraAssets.CrewmateFile.LoadAsset(); - } + roleAddButton.Text.fontSizeMin = 1; + roleAddButton.FileImage.sprite = role.IsImpostor + ? MiraAssets.ImpostorFile.LoadAsset() + : MiraAssets.CrewmateFile.LoadAsset(); __instance.AddFileAsChildCustom(roleAddButton, ref num, ref num2, ref num3); if (roleAddButton != null && roleAddButton.Button != null) @@ -410,19 +402,16 @@ public static class TaskAdderPatches var roleAddButton = Object.Instantiate(__instance.RoleButton); roleAddButton.MyTask = null; roleAddButton.SafePositionWorld = __instance.SafePositionWorld; - roleAddButton.Text.text = prettyEnabled ? roleBehaviour.GetRoleName() : "Be_" + roleBehaviour.GetRoleName() + ".exe"; + roleAddButton.Text.text = roleBehaviour.GetRoleName(); roleAddButton.Text.EnableMasking(); roleAddButton.role = roleBehaviour; - if (prettyEnabled) - { - roleAddButton.Text.fontSizeMin = 1; - roleAddButton.FileImage.sprite = roleBehaviour.IsImpostor - ? MiraAssets.ImpostorFile.LoadAsset() - : MiraAssets.CrewmateFile.LoadAsset(); - } + roleAddButton.Text.fontSizeMin = 1; + roleAddButton.FileImage.sprite = roleBehaviour.IsImpostor + ? MiraAssets.ImpostorFile.LoadAsset() + : MiraAssets.CrewmateFile.LoadAsset(); if (roleBehaviour is ICustomRole custom) { - if (prettyEnabled && custom.Team == ModdedRoleTeams.Custom) roleAddButton.FileImage.sprite = MiraAssets.CustomTeamFile.LoadAsset(); + if (custom.Team == ModdedRoleTeams.Custom) roleAddButton.FileImage.sprite = MiraAssets.CustomTeamFile.LoadAsset(); var customColor = custom.IntroConfiguration?.IntroTeamColor ?? Color.gray; if (custom.Team is ModdedRoleTeams.Crewmate) { diff --git a/MiraAPI/Patches/GameModes/HudPatches.cs b/MiraAPI/Patches/GameModes/HudPatches.cs index dba4731..4426e0a 100644 --- a/MiraAPI/Patches/GameModes/HudPatches.cs +++ b/MiraAPI/Patches/GameModes/HudPatches.cs @@ -50,7 +50,7 @@ internal static class HudPatches gmText.SetActive(false); modelText.SetActive(false); _text = gmTextClone.GetComponent(); - _text.text = CustomGameModeManager.ActiveMode != null ? CustomGameModeManager.ActiveMode.ColoredName : MiraLocaleManager.Get("gamemode.classic"); + _text.text = CustomGameModeManager.ActiveMode != null ? CustomGameModeManager.ActiveMode.ColoredName : MiraLocaleManager.Get("MiraApi.Gamemode.Classic"); CustomGameModeManager.ActiveMode?.HudStart(instance); } diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index 345c9a6..f8835a8 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -42,12 +42,16 @@ public static class HudManagerPatches ResizeUI(LocalSettingsTabSingleton.Instance.ButtonUIFactorSlider.Value); } + private static Transform _storedButtonsParent; public static void ResizeUI(float scaleFactor) { - var baseButtons = HudManager.Instance.transform.FindChild("Buttons"); - if (baseButtons != null) + if (!_storedButtonsParent) { - foreach (var aspect in baseButtons.GetComponentsInChildren(true)) + _storedButtonsParent = HudManager.Instance.transform.FindChild("Buttons"); + } + if (_storedButtonsParent) + { + foreach (var aspect in _storedButtonsParent.GetComponentsInChildren(true)) { if (!aspect.gameObject) { @@ -82,9 +86,9 @@ public static class HudManagerPatches button.gameObject.SetActive(!button.isActiveAndEnabled); } - if (baseButtons != null) + if (_storedButtonsParent) { - foreach (var arrange in baseButtons.GetComponentsInChildren(true)) + foreach (var arrange in _storedButtonsParent.GetComponentsInChildren(true)) { if (!arrange.gameObject || !arrange.transform) { @@ -281,6 +285,8 @@ public static class HudManagerPatches } MiraApiSettings.OldButtonScaleFactor = LocalSettingsTabSingleton.Instance.ButtonUIFactorSlider.Value; + MiraApiSettings.OldUiButtonScaleFactor = + LocalSettingsTabSingleton.Instance.TopRightButtonsFactorSlider.Value; Coroutines.Start(CoResizeUI()); } diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index dec15b6..46108fc 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -716,7 +716,7 @@ internal static class GameOptionsMenuPatch categoryHeaderMasked.gameObject.SetActive(false); var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); - newText.text = $"({"gamesetting.global.clicktoclose".Translate()})"; + newText.text = $"({"MiraApi.GameSetting.Global.ClickToClose".Translate()})"; newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); newText.gameObject.GetComponent().Destroy(); @@ -733,8 +733,8 @@ internal static class GameOptionsMenuPatch { group.AllOptionsHidden = !group.AllOptionsHidden; newText.text = group.AllOptionsHidden - ? $"({"gamesetting.global.clicktoopen".Translate()})" - : $"({"gamesetting.global.clicktoclose".Translate()})"; + ? $"({"MiraApi.GameSetting.Global.ClickToOpen".Translate()})" + : $"({"MiraApi.GameSetting.Global.ClickToClose".Translate()})"; })); headerBtn.SetButtonEnableState(true); diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index 6824077..c1db00b 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -46,7 +46,7 @@ internal static class GamePresetsTabPatches if (MenuState.Instance.CurrentModIdx != 0) { - __instance.PresetDescriptionText.text = "gamesetting.preset.hint".Translate(); + __instance.PresetDescriptionText.text = "MiraApi.GameSetting.Preset.Hint".Translate(); } else { @@ -185,7 +185,7 @@ internal static class GamePresetsTabPatches // set the button text and alignment var saveText = saveButton.buttonText; - saveText.text = "gamesetting.preset.save".Translate(); + saveText.text = "MiraApi.GameSetting.Preset.Save".Translate(); saveText.GetComponent().Destroy(); saveText.alignment = TextAlignmentOptions.Center; saveText.transform.parent.localPosition = new Vector3( diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 7c673d0..3435277 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -83,7 +83,7 @@ public static class LobbyViewPanePatches var pos = ModifiersTabButton.transform.localPosition; pos.x = 2.1f; ModifiersTabButton.transform.localPosition = pos; - ModifiersTabButton.buttonText.text = "gamesetting.modifiers".Translate(); + ModifiersTabButton.buttonText.text = "MiraApi.GameSetting.Modifiers".Translate(); ModifiersTabButton.OnClick = new Button.ButtonClickedEvent(); ModifiersTabButton.OnClick.AddListener( (UnityAction)(() => @@ -151,7 +151,7 @@ public static class LobbyViewPanePatches var cgm = GetCustomGamemode(); ModifiersTabButton?.gameObject.SetActive(false); menu.rolesTabButton.gameObject.SetActive(cgm is null or { ShowNormalRoleSettings: true }); - menu.gameModeText.text = cgm is null ? MiraLocaleManager.Get("gamemode.classic") : cgm.ColoredName; + menu.gameModeText.text = cgm is null ? MiraLocaleManager.Get("MiraApi.Gamemode.Classic") : cgm.ColoredName; } else { diff --git a/MiraAPI/Patches/Options/MenuState.cs b/MiraAPI/Patches/Options/MenuState.cs index d7b8e11..e21e364 100644 --- a/MiraAPI/Patches/Options/MenuState.cs +++ b/MiraAPI/Patches/Options/MenuState.cs @@ -150,7 +150,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) _largeButtons[MenuButton.Modifiers] = mBtn; mBtn.name = "ModifiersButton"; mBtn.buttonText.gameObject.GetComponent().Destroy(); - mBtn.buttonText.text = "gamesetting.modifiers".Translate(); + mBtn.buttonText.text = "MiraApi.GameSetting.Modifiers".Translate(); mBtn.OnClick = new Button.ButtonClickedEvent(); mBtn.OnClick.AddListener( (UnityAction)(() => @@ -219,7 +219,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) })); var roleText = smRolesBtn.buttonText; - roleText.text = "gamesetting.roles".Translate(); + roleText.text = "MiraApi.GameSetting.Roles".Translate(); roleText.GetComponent().Destroy(); roleText.alignment = TextAlignmentOptions.Center; roleText.transform.parent.localPosition = new Vector3( @@ -244,7 +244,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) var smModBtn = Instantiate(smRolesBtn, smRolesBtn.transform.parent); _smallButtons[MenuButton.Modifiers] = smModBtn; smModBtn.name = "SmallModifiersButton"; - smModBtn.buttonText.text = "gamesetting.modifiers".Translate(); + smModBtn.buttonText.text = "MiraApi.GameSetting.Modifiers".Translate(); smModBtn.OnClick = new Button.ButtonClickedEvent(); smModBtn.OnClick.AddListener( (UnityAction)(() => diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index ff31dfa..390adfb 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -289,8 +289,8 @@ public static class RoleSettingMenuPatches quotaInst.gameObject.SetActive(!RoleGroupHidden[group]); var label = RoleGroupHidden[group] - ? $"({"gamesetting.global.clicktoopen".Translate()})" - : $"({"gamesetting.global.clicktoclose".Translate()})"; + ? $"({"MiraApi.GameSetting.Global.ClickToOpen".Translate()})" + : $"({"MiraApi.GameSetting.Global.ClickToClose".Translate()})"; var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); newText.text = $"{label}"; newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); @@ -610,7 +610,7 @@ public static class RoleSettingMenuPatches comp.Destroy(); } - categoryHeaderMasked.Title.text = "miraApi.returnToRoleSettings".Translate(); + categoryHeaderMasked.Title.text = "MiraApi.ReturnToRoleSettings".Translate(); if (!categoryHeaderMasked.gameObject.TryGetComponent(out _)) { diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index 68c78c9..742db18 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -1,44 +1,42 @@ - Mira API - Visuals/UI - Freeplay - Accessibility - MiraAPI - Show Modifiers HUD on Left Side - Show Keybinds on Buttons - Button Scale Factor - Top-Right UI Scale Factor - Max FPS - Display Wiki on Bottom Row - Pretty Task Laptop - Enable Sabotage Flashes - Enable Sabotage Blare - RETURN TO ROLE SETTINGS - Custom Category 1 - Custom Category 2 - Configure modifiers and their settings here! - Apply game settings for this mod! - Classic - The classic Among Us experience! + Mira API + Visuals/UI + Accessibility + MiraAPI + Show Modifiers HUD on Left Side + Show Keybinds on Buttons + Button Scale Factor + Top-Right UI Scale Factor + Max FPS + Display Wiki on Bottom Row + Enable Sabotage Flashes + Enable Sabotage Blare + RETURN TO ROLE SETTINGS + Custom Category 1 + Custom Category 2 + Configure modifiers and their settings here! + Apply game settings for this mod! + Classic + The classic Among Us experience! Find the Impostors or deceive the Crew. - Hide n Seek - You can run, but you can't hide! + Hide n Seek + You can run, but you can't hide! Outlive the timer or eliminate the crew. - Click to close - Click to open - Select or create a options preset for this game - Save - Roles - Modifiers - Crewmate - Crew - Crewmate Roles - Impostor - Imp - Impostor Roles - Neutral - Neut - Neutral Roles - Other + Click to close + Click to open + Select or create a options preset for this game + Save + Roles + Modifiers + Crewmate + Crew + Crewmate Roles + Impostor + Imp + Impostor Roles + Neutral + Neut + Neutral Roles + Other diff --git a/MiraAPI/Resources/Locale/zh_CN.xml b/MiraAPI/Resources/Locale/zh_CN.xml index 89848c4..12af14c 100644 --- a/MiraAPI/Resources/Locale/zh_CN.xml +++ b/MiraAPI/Resources/Locale/zh_CN.xml @@ -1,27 +1,25 @@ - Mira API - 界面/视觉 - 自由模式 - 无障碍 - MiraAPI - 在左侧显示附加职业UI - 在按钮上显示快捷键 - 按钮缩放比例 - 最大帧数 - 美化任务笔记本 - 启用破坏红色闪烁 - 启用破坏音效 - 返回职业设置 - 自定义类别 1 - 自定义类别 2 - 在此配置附加职业及其设置! - 为此模组应用游戏设置! + Mira API + 界面/视觉 + 无障碍 + MiraAPI + 在左侧显示附加职业UI + 在按钮上显示快捷键 + 按钮缩放比例 + 最大帧数 + 启用破坏红色闪烁 + 启用破坏音效 + 返回职业设置 + 自定义类别 1 + 自定义类别 2 + 在此配置附加职业及其设置! + 为此模组应用游戏设置! 默认 默认原版游戏模式 - 点击关闭 - 为本游戏选择或创建一个预设 - 保存 - 职业 - 附加职业 + 点击关闭 + 为本游戏选择或创建一个预设 + 保存 + 职业 + 附加职业 diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index c214619..8f62a7f 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -74,10 +74,10 @@ public interface ICustomRole : IOptionable /// string RoleFactionTitle => Team switch { - ModdedRoleTeams.Crewmate => MiraLocaleManager.Get("roleTeam.crewmate"), - ModdedRoleTeams.Impostor => MiraLocaleManager.Get("roleTeam.impostor"), - ModdedRoleTeams.Custom => MiraLocaleManager.Get("roleTeam.neutral"), - _ => MiraLocaleManager.Get("roleTeam.other"), + ModdedRoleTeams.Crewmate => MiraLocaleManager.Get("MiraApi.RoleTeam.Crewmate"), + ModdedRoleTeams.Impostor => MiraLocaleManager.Get("MiraApi.RoleTeam.Impostor"), + ModdedRoleTeams.Custom => MiraLocaleManager.Get("MiraApi.RoleTeam.Neutral"), + _ => MiraLocaleManager.Get("MiraApi.RoleTeam.Other"), }; /// diff --git a/MiraAPI/Roles/RoleOptionsGroup.cs b/MiraAPI/Roles/RoleOptionsGroup.cs index a53ea95..f8f25f5 100644 --- a/MiraAPI/Roles/RoleOptionsGroup.cs +++ b/MiraAPI/Roles/RoleOptionsGroup.cs @@ -13,15 +13,15 @@ public record struct RoleOptionsGroup(string Name, Color Color, int Priority = 0 /// /// Gets the default group for crewmates. /// - public static RoleOptionsGroup Crewmate { get; } = new("roleTeam.crewmate.group", Palette.CrewmateBlue, -2); + public static RoleOptionsGroup Crewmate { get; } = new("MiraApi.RoleTeam.Crewmate.Group", Palette.CrewmateBlue, -2); /// /// Gets the default group for impostors. /// - public static RoleOptionsGroup Impostor { get; } = new("roleTeam.impostor.group", Palette.ImpostorRed, -1); + public static RoleOptionsGroup Impostor { get; } = new("MiraApi.RoleTeam.Impostor.Group", Palette.ImpostorRed, -1); /// /// Gets the default group for neutral roles. /// - public static RoleOptionsGroup Neutral { get; } = new("roleTeam.neutral.group", Color.gray); + public static RoleOptionsGroup Neutral { get; } = new("MiraApi.RoleTeam.Neutral.Group", Color.gray); } From 48aff163f9b2dad0d9ed3427c525970012a3ad33 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:29:12 -0500 Subject: [PATCH 14/16] Fix issues with local settings tab localization --- MiraAPI/LocalSettings/ILocalSetting.cs | 5 +++ MiraAPI/LocalSettings/LocalSettingsTab.cs | 22 ++++++++++-- .../SettingTypes/LocalEnumSetting.cs | 18 +++++++++- .../SettingTypes/LocalNumberSetting.cs | 16 +++++++++ .../SettingTypes/LocalSettingBase.cs | 3 ++ .../SettingTypes/LocalSliderSetting.cs | 9 +++++ .../SettingTypes/LocalToggleSetting.cs | 10 ++++++ .../LocalSettings/OptionsMenuPatches.cs | 2 ++ MiraAPI/Translation/MiraLocaleManager.cs | 35 +++++++++++++++---- 9 files changed, 110 insertions(+), 10 deletions(-) diff --git a/MiraAPI/LocalSettings/ILocalSetting.cs b/MiraAPI/LocalSettings/ILocalSetting.cs index b51a13d..9bc4bbd 100644 --- a/MiraAPI/LocalSettings/ILocalSetting.cs +++ b/MiraAPI/LocalSettings/ILocalSetting.cs @@ -39,4 +39,9 @@ public interface ILocalSetting /// Whether it's the last on the row. /// The created setting's . GameObject? CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last); + + /// + /// Used to refresh the setting's information, usually for localization. + /// + void RefreshOption(); } diff --git a/MiraAPI/LocalSettings/LocalSettingsTab.cs b/MiraAPI/LocalSettings/LocalSettingsTab.cs index 7fc7e1d..e22f834 100644 --- a/MiraAPI/LocalSettings/LocalSettingsTab.cs +++ b/MiraAPI/LocalSettings/LocalSettingsTab.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using BepInEx.Configuration; using HarmonyLib; +using MiraAPI.LocalSettings.SettingTypes; using MiraAPI.Patches.LocalSettings; using MiraAPI.Translation; using MiraAPI.Utilities; @@ -53,6 +54,11 @@ public abstract class LocalSettingsTab(ConfigFile config) /// public List Settings { get; } = []; + /// + /// Gets a collection of s alongside the locale keys for them. + /// + public Dictionary Labels { get; } = []; + /// /// Gets the list of s. /// @@ -74,12 +80,21 @@ public abstract class LocalSettingsTab(ConfigFile config) } /// - /// Attempts to open the tab. + /// Refreshes setting names upon opening the tab. /// Override for custom behavior. /// public virtual void Open() { - OptionsMenuPatches.Instance?.OpenTabGroup(TabIndex); + foreach (var setting in Settings) + { + setting.RefreshOption(); + } + + foreach (var label in Labels) + { + label.Key.text = + $"{label.Value.Translate()}"; + } } /// @@ -273,7 +288,7 @@ public abstract class LocalSettingsTab(ConfigFile config) return tabButtonObject.gameObject; } - private static void CreateLabel(GameObject template, Transform parent, string text, ref float offset) + private void CreateLabel(GameObject template, Transform parent, string text, ref float offset) { var label = Object.Instantiate(template, parent); label.transform.localPosition = new Vector3(0, 1.85f - offset); @@ -281,6 +296,7 @@ public abstract class LocalSettingsTab(ConfigFile config) label.name = text; var tmp = label.GetComponent(); + Labels.Add(tmp, text); tmp.text = $"{text.Translate()}"; tmp.alignment = TextAlignmentOptions.Center; diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs index ac0f858..e1e0c7b 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs @@ -26,6 +26,10 @@ public class LocalEnumSetting : LocalSettingBase /// public string[] Values { get; } + private SpriteRenderer _highlight { get; set; } + + private TextMeshPro _btnText { get; set; } + /// /// Initializes a new instance of the class. /// @@ -54,6 +58,7 @@ public class LocalEnumSetting : LocalSettingBase { var button = Object.Instantiate(toggle, parent).GetComponent(); var tmp = button.transform.FindChild("Text_TMP").GetComponent(); + _btnText = tmp; var rollover = button.GetComponent(); tmp.GetComponent().Destroy(); button.gameObject.SetActive(true); @@ -63,6 +68,7 @@ public class LocalEnumSetting : LocalSettingBase var highlight = button.transform.FindChild("ButtonHighlight")?.GetComponent(); if (highlight != null) { + _highlight = highlight; highlight.color = Tab!.TabAppearance.EnumHoverColor; highlight.gameObject.SetActive(false); } @@ -125,9 +131,19 @@ public class LocalEnumSetting : LocalSettingBase return button.gameObject; } + /// + public override void RefreshOption() + { + _btnText.text = GetValueText(); + if (_highlight) + { + _highlight.gameObject.SetActive(false); + } + } + /// protected override string GetValueText() { - return $"{Name.Translate()}: {Values[GetValue()]}"; + return $"{Name.Translate()}: {Values[GetValue()].Translate()}"; } } diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs index 75d1fde..f37ea80 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs @@ -35,6 +35,10 @@ public class LocalNumberSetting : LocalSettingBase /// public MiraNumberSuffixes SuffixType { get; } + private SpriteRenderer _highlight { get; set; } + + private TextMeshPro _btnText { get; set; } + /// /// Initializes a new instance of the class. /// @@ -65,6 +69,7 @@ public class LocalNumberSetting : LocalSettingBase { var button = Object.Instantiate(toggle, parent).GetComponent(); var tmp = button.transform.FindChild("Text_TMP").GetComponent(); + _btnText = tmp; var rollover = button.GetComponent(); tmp.GetComponent().Destroy(); button.gameObject.SetActive(true); @@ -74,6 +79,7 @@ public class LocalNumberSetting : LocalSettingBase var highlight = button.transform.FindChild("ButtonHighlight")?.GetComponent(); if (highlight != null) { + _highlight = highlight; highlight.color = Tab!.TabAppearance.NumberHoverColor; highlight.gameObject.SetActive(false); } @@ -136,6 +142,16 @@ public class LocalNumberSetting : LocalSettingBase return button.gameObject; } + /// + public override void RefreshOption() + { + _btnText.text = GetValueText(); + if (_highlight) + { + _highlight.gameObject.SetActive(false); + } + } + /// protected override string GetValueText() { diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalSettingBase.cs b/MiraAPI/LocalSettings/SettingTypes/LocalSettingBase.cs index d7c2c64..e26f6ce 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalSettingBase.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalSettingBase.cs @@ -54,6 +54,9 @@ public abstract class LocalSettingBase : ILocalSetting /// The created setting. public abstract GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last); + /// + public abstract void RefreshOption(); + /// /// Returns the formated string to use in the text of the setting. /// diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs index eb59cce..2a90bfd 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalSliderSetting.cs @@ -40,6 +40,8 @@ public class LocalSliderSetting : LocalSettingBase /// public MiraNumberSuffixes SuffixType { get; } + private SlideBar _slider { get; set; } + /// /// Initializes a new instance of the class. /// @@ -71,6 +73,7 @@ public class LocalSliderSetting : LocalSettingBase public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last) { var newSlider = Object.Instantiate(slider, parent).GetComponent(); + _slider = newSlider; var rollover = newSlider.GetComponent(); newSlider.Title = newSlider.transform.FindChild("Text_TMP").GetComponent(); // Why the hell slider has a title property that is not even assigned??? newSlider.Title.GetComponent().Destroy(); @@ -105,6 +108,12 @@ public class LocalSliderSetting : LocalSettingBase return newSlider.gameObject; } + /// + public override void RefreshOption() + { + _slider.Title.text = GetValueText(); + } + /// protected override string GetValueText() { diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs index 3cb02e3..f28b6db 100644 --- a/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs +++ b/MiraAPI/LocalSettings/SettingTypes/LocalToggleSetting.cs @@ -15,6 +15,8 @@ namespace MiraAPI.LocalSettings.SettingTypes; /// public class LocalToggleSetting : LocalSettingBase { + private ToggleButtonBehaviour _toggle { get; set; } + /// /// Initializes a new instance of the class. /// @@ -32,6 +34,7 @@ public class LocalToggleSetting : LocalSettingBase public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last) { var toggleObject = Object.Instantiate(toggle, parent).GetComponent(); + _toggle = toggleObject; var tmp = toggleObject.transform.FindChild("Text_TMP").GetComponent(); var passiveButton = toggleObject.GetComponent(); var rollover = toggleObject.GetComponent(); @@ -86,4 +89,11 @@ public class LocalToggleSetting : LocalSettingBase return toggleObject.gameObject; } + + /// + public override void RefreshOption() + { + _toggle.UpdateText(GetValue()); + _toggle.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor; + } } diff --git a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index a8cab15..75edd5e 100644 --- a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs +++ b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs @@ -213,6 +213,8 @@ public static class OptionsMenuPatches if (background != null) background.color = tab.TabAppearance.TabColor; + + tab.Open(); } private static void CustomClose(LocalSettingsTab tab) { diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs index 9f9bfd7..a34f22f 100644 --- a/MiraAPI/Translation/MiraLocaleManager.cs +++ b/MiraAPI/Translation/MiraLocaleManager.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Xml; +using MiraAPI.Utilities; using Reactor.Localization.Utilities; using UnityEngine; @@ -97,7 +98,22 @@ public static class MiraLocaleManager var callingAssembly = Assembly.GetCallingAssembly(); var dir = GetModLangDir(modGuid); - LoadInternalStrings(callingAssembly, modGuid, dir); + LoadInternalStrings(callingAssembly, callingAssembly.GetName().Name!, modGuid, dir); + } + + /// + /// 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(). + /// + /// The mod GUID (e.g., "mira.example"). + /// The mod's root namespace (e.g., "mira.api"). + public static void Register(string modGuid, string internalName) + { + var callingAssembly = Assembly.GetCallingAssembly(); + var dir = GetModLangDir(modGuid); + + LoadInternalStrings(callingAssembly, internalName, modGuid, dir); } /// @@ -194,7 +210,7 @@ public static class MiraLocaleManager return newString; } - private static void LoadInternalStrings(Assembly assembly, string modGuid, string dir) + private static void LoadInternalStrings(Assembly assembly, string internalName, string modGuid, string dir) { Directory.CreateDirectory(dir); @@ -203,8 +219,9 @@ public static class MiraLocaleManager Locale[modGuid] = []; } - var resourcePrefix = $"{assembly.GetName().Name}.Resources.Locale."; + var resourcePrefix = $"{internalName}.Resources.Locale."; + var atLeastOneLoaded = false; foreach (var locale in LangList) { using var resourceStream = @@ -221,7 +238,8 @@ public static class MiraLocaleManager { var dict = ParseXmlFile(xmlContent); Locale[modGuid][locale.Key] = dict; - Info($"Loaded {locale.Key} translation for mod {modGuid} ({dict.Count} keys)"); + Info($"Loaded {locale.Key.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)"); + atLeastOneLoaded = true; } catch (Exception e) { @@ -229,6 +247,11 @@ public static class MiraLocaleManager } } + if (!atLeastOneLoaded) + { + Error($"No internal strings were found for {internalName}!"); + } + if (!Locale.TryGetValue(modGuid, out _)) { Locale[modGuid] = []; @@ -247,11 +270,11 @@ public static class MiraLocaleManager { var dict = ParseXmlFile(filePath); Locale[modGuid][lang] = dict; - Info($"Loaded {lang} translation for mod {modGuid} ({dict.Count} keys)"); + Info($"Loaded external {lang.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)"); } catch (Exception e) { - Error($"Failed to load translation {filePath}: {e.Message}"); + Error($"Failed to load external translation {filePath}: {e.Message}"); } } } From a3ed901a185904edc1ddffba256af02846c1e136 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:36:33 -0500 Subject: [PATCH 15/16] Translate local setting buttons --- MiraAPI/LocalSettings/LocalSettingsButton.cs | 8 ++++++++ MiraAPI/LocalSettings/LocalSettingsTab.cs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/MiraAPI/LocalSettings/LocalSettingsButton.cs b/MiraAPI/LocalSettings/LocalSettingsButton.cs index 548488f..747b1ce 100644 --- a/MiraAPI/LocalSettings/LocalSettingsButton.cs +++ b/MiraAPI/LocalSettings/LocalSettingsButton.cs @@ -29,6 +29,8 @@ public class LocalSettingsButton(string text, Action onClick) /// public LocalSettingsTab? Tab { get; internal set; } + private TextMeshPro _btnText { get; set; } + internal GameObject CreateButton(ToggleButtonBehaviour toggle, Transform parent, ref float offset, ref int order, bool last) { var button = Object.Instantiate(toggle, parent).GetComponent(); @@ -53,6 +55,7 @@ public class LocalSettingsButton(string text, Action onClick) } tmp.text = Text.Translate(); + _btnText = tmp; button.name = Text; button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); rollover.OutColor = Tab!.TabAppearance.ButtonColor; @@ -75,4 +78,9 @@ public class LocalSettingsButton(string text, Action onClick) return button.gameObject; } + + public void RefreshButton() + { + _btnText.text = Text.Translate(); + } } diff --git a/MiraAPI/LocalSettings/LocalSettingsTab.cs b/MiraAPI/LocalSettings/LocalSettingsTab.cs index e22f834..ccd1f5c 100644 --- a/MiraAPI/LocalSettings/LocalSettingsTab.cs +++ b/MiraAPI/LocalSettings/LocalSettingsTab.cs @@ -89,6 +89,10 @@ public abstract class LocalSettingsTab(ConfigFile config) { setting.RefreshOption(); } + foreach (var button in Buttons) + { + button.RefreshButton(); + } foreach (var label in Labels) { From e532e558b694906c7124137b687b239956a4fc26 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:49:10 -0500 Subject: [PATCH 16/16] Fix local tabs not being localized without hovering --- MiraAPI/LocalSettings/LocalSettingsTab.cs | 8 +++++++- MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs | 1 + MiraAPI/Translation/MiraLocalizationProvider.cs | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/MiraAPI/LocalSettings/LocalSettingsTab.cs b/MiraAPI/LocalSettings/LocalSettingsTab.cs index ccd1f5c..eace08f 100644 --- a/MiraAPI/LocalSettings/LocalSettingsTab.cs +++ b/MiraAPI/LocalSettings/LocalSettingsTab.cs @@ -49,6 +49,11 @@ public abstract class LocalSettingsTab(ConfigFile config) /// public TabGroup? TabButton { get; internal set; } + /// + /// Gets a collection of s alongside the locale keys for them. + /// + public static Dictionary TabGroups { get; } = []; + /// /// Gets the list of s. /// @@ -237,6 +242,7 @@ public abstract class LocalSettingsTab(ConfigFile config) tabButtonText.transform.localScale = new Vector3(0.9f, 0.9f); tabButtonText.alignment = TextAlignmentOptions.Right; tabButtonText.text = $"{GetShortName(TabName.Translate())}"; + TabGroups.Add(tabButtonText, TabName); var tabButton = tabButtonObject.GetComponent(); var rollover = tabButtonObject.Rollover; @@ -311,7 +317,7 @@ public abstract class LocalSettingsTab(ConfigFile config) offset += 0.5f; } - private static string GetShortName(string name) + internal static string GetShortName(string name) { var shortName = string.Empty; name.Split(' ').Do(x => shortName += x[0]); diff --git a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index 75edd5e..5b89fa8 100644 --- a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs +++ b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs @@ -69,6 +69,7 @@ public static class OptionsMenuPatches int i = 0; int tabIdx = 0; int page = 1; + LocalSettingsTab.TabGroups.Clear(); foreach (var settings in LocalSettingsManager.AvailableTabs) { var tab = settings.CreateTab(__instance); diff --git a/MiraAPI/Translation/MiraLocalizationProvider.cs b/MiraAPI/Translation/MiraLocalizationProvider.cs index 7505855..47cf747 100644 --- a/MiraAPI/Translation/MiraLocalizationProvider.cs +++ b/MiraAPI/Translation/MiraLocalizationProvider.cs @@ -1,5 +1,6 @@ using System.Linq; using Il2CppInterop.Runtime.InteropTypes.Arrays; +using MiraAPI.LocalSettings; using Reactor.Localization; using Reactor.Localization.Providers; using Reactor.Utilities; @@ -38,6 +39,16 @@ public class MiraLocalizationProvider : LocalizationProvider { MiraApiPlugin.Culture = new(culture); } + + foreach (var tab in LocalSettingsTab.TabGroups) + { + if (tab.Key == null) + { + break; + } + + tab.Key.text = $"{LocalSettingsTab.GetShortName(tab.Value.Translate())}"; + } /*Warning($""); Warning($""); foreach (var stringName in TranslationController.Instance.currentLanguage.AllStrings)