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] 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; }