diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 4da744d..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 => "Freeze"; + 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 8ca1b82..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 => "Call Meeting"; + 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 3763577..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 => "Win Game"; + 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 b59d387..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 => "Teleport"; + 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 a6f6855..71013e9 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"; 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".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..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 => "Mira Example"; + public override string TabName => "settings.miraExample"; 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 dc0783b..4ff4c8e 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -3,11 +3,10 @@ using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using HarmonyLib; using MiraAPI.PluginLoading; -using MiraAPI.Utilities.Assets; +using MiraAPI.Translation; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; -using UnityEngine; namespace MiraAPI.Example; @@ -18,6 +17,10 @@ namespace MiraAPI.Example; [ReactorModFlags(ModFlags.RequireOnAllClients)] public partial class ExamplePlugin : BasePlugin, IMiraPlugin { + public ExamplePlugin() + { + MiraLocaleManager.Register("mira.example"); + } public Harmony Harmony { get; } = new(Id); public string OptionsTitleText => "Mira API\nExample Mod"; public string CustomOptionMenuNameTwo => "angxl's Options"; 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/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.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 9e20b56..20e3872 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"; 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..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 => "Freezed"; + 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 f2a1e03..a637df5 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"; 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..aff6ccf 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"; 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..6acba04 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"; 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..e850385 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"; - [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..f6296ba 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"; - [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..8d0f82d 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"; 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..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 => "Custom Role"; + public override string GroupName => "ApiExample.Role.Freezer"; - [ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)] + [ModdedNumberOption("ApiExample.Role.Freezer.Settings.FreezeDuration", 1, 15, 1, MiraNumberSuffixes.Seconds)] public float FreezeDuration { get; set; } = 5; - [ModdedNumberOption("Freeze Uses", 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 d05569b..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 => "Teleporter"; + public override string GroupName => "ApiExample.Role.Teleporter"; - public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 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("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)] + [ModdedNumberOption("ApiExample.Role.Teleporter.Settings.TeleportDuration", 5, 25, 1, MiraNumberSuffixes.Seconds)] public float TeleportDuration { get; set; } = 10; - [ModdedNumberOption("Zoom Distance", 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 new file mode 100644 index 0000000..5951728 --- /dev/null +++ b/MiraAPI.Example/Resources/Locale/en_US.xml @@ -0,0 +1,82 @@ + + + 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 + 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 new file mode 100644 index 0000000..aa9f7eb --- /dev/null +++ b/MiraAPI.Example/Resources/Locale/zh_CN.xml @@ -0,0 +1,68 @@ + + + 传送者 + 缩小视野并在地图上传送! + 缩小视野并在地图上传送! + 缩小视野并在地图上传送! + 冰冻者 + 将其他玩家冰冻一段时间。 + 市长 + 你拥有额外投票权! + 变色龙 + 静止不动时保持隐身。 + 流放杀手 + 可以击杀的流放者。 + 流放者 + 流放者 + 你是流放者,你没有队伍。 + 思考 + 思考中... + 冰冻 + 传送 + 召集会议 + 赢得游戏 + 队长 + 你可以在地图上任意位置召集会议。 + 已被冰冻 + 高优先级 + 你是高优先级!老实说我也不太清楚这是什么意思…… + 示例选项组 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..e3f91b7 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"; - public string RoleLongDescription => "Stay invisible while not moving."; - public string RoleDescription => RoleLongDescription; + 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 5a7f4bd..d365b37 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"; - public string RoleLongDescription => "Freeze another player for a duration of time."; - public string RoleDescription => RoleLongDescription; + 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 42c264f..6dae223 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"; - public string RoleLongDescription => "You get extra votes!"; - public string RoleDescription => RoleLongDescription; + 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 1afa4eb..9ccec19 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 => "Outcast Killer"; - public string RoleDescription => "Outcast who can kill."; - public string RoleLongDescription => RoleDescription; + public string IdPart => "ApiExample.Role.OutcastKiller"; public Color RoleColor => Color.magenta; public ModdedRoleTeams Team => ModdedRoleTeams.Custom; @@ -21,13 +19,13 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole CanUseVent = true, }; - public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray); + public RoleOptionsGroup RoleOptionsGroup { get; } = new("outcast", 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..8c114b8 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"; - public string RoleLongDescription => "Zoom out and teleport across the map!"; - public string RoleDescription => RoleLongDescription; + public string IdPart => "ApiExample.Role.Teleporter"; public Color RoleColor => new Color32(221, 176, 152, 255); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; 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 fe7e725..3d25b33 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -20,7 +20,8 @@ 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 => + x.Name).ToArray(); Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors); Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors); diff --git a/MiraAPI/GameModes/AbstractGameMode.cs b/MiraAPI/GameModes/AbstractGameMode.cs index 6fa2d8c..10dd377 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; @@ -32,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; @@ -42,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 => $"{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 44b43da..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 => "Classic"; + public override string Name => "MiraApi.Gamemode.Classic"; /// - public override string Description => "The classic Among Us experience!\nFind the Impostors or deceive the Crew."; + public override string Description => "MiraApi.Gamemode.Classic.Description"; public override LoadableAsset? Icon => MiraAssets.ClassicGamemodeIcon; } diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index 30877c3..254bf6d 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MiraAPI.Translation; using MiraAPI.PluginLoading; using Reactor.Utilities; @@ -58,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)); /// diff --git a/MiraAPI/GameModes/GameModeOption.cs b/MiraAPI/GameModes/GameModeOption.cs index 95acd9c..3e0d2f7 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("MiraApi.Gamemode.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.Name)); } /*[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))] [HarmonyPostfix] diff --git a/MiraAPI/GameModes/HideAndSeekMode.cs b/MiraAPI/GameModes/HideAndSeekMode.cs index d5c90e8..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 => "Hide n Seek"; + public override string Name => "MiraApi.Gamemode.HideAndSeek"; /// - public override string Description => "You can run, but you can't hide!\nOutlive the timer or eliminate the crew."; + 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/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/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 1df4921..5fa0ceb 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -2,6 +2,7 @@ using System; 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 = MiraLocaleManager.GetOrCreateLocaleString(Title); 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/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/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/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/LocalSettingsButton.cs b/MiraAPI/LocalSettings/LocalSettingsButton.cs index 5a1f371..747b1ce 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; @@ -28,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(); @@ -51,7 +54,8 @@ 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(); + _btnText = tmp; button.name = Text; button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); rollover.OutColor = Tab!.TabAppearance.ButtonColor; @@ -74,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 074d7db..eace08f 100644 --- a/MiraAPI/LocalSettings/LocalSettingsTab.cs +++ b/MiraAPI/LocalSettings/LocalSettingsTab.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using BepInEx.Configuration; using HarmonyLib; +using MiraAPI.LocalSettings.SettingTypes; using MiraAPI.Patches.LocalSettings; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -47,11 +49,21 @@ 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. /// public List Settings { get; } = []; + /// + /// Gets a collection of s alongside the locale keys for them. + /// + public Dictionary Labels { get; } = []; + /// /// Gets the list of s. /// @@ -73,12 +85,25 @@ 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 button in Buttons) + { + button.RefreshButton(); + } + + foreach (var label in Labels) + { + label.Key.text = + $"{label.Value.Translate()}"; + } } /// @@ -216,7 +241,8 @@ 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())}"; + TabGroups.Add(tabButtonText, TabName); var tabButton = tabButtonObject.GetComponent(); var rollover = tabButtonObject.Rollover; @@ -244,7 +270,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,13 +292,13 @@ 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; } - 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); @@ -280,7 +306,8 @@ public abstract class LocalSettingsTab(ConfigFile config) label.name = text; var tmp = label.GetComponent(); - tmp.text = $"{text}"; + Labels.Add(tmp, text); + tmp.text = $"{text.Translate()}"; tmp.alignment = TextAlignmentOptions.Center; var meshRend = label.GetComponent(); @@ -290,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/LocalSettings/SettingTypes/LocalEnumSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalEnumSetting.cs index 78b0278..e1e0c7b 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; @@ -25,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. /// @@ -53,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); @@ -62,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); } @@ -124,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}: {Values[GetValue()]}"; + return $"{Name.Translate()}: {Values[GetValue()].Translate()}"; } } diff --git a/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs b/MiraAPI/LocalSettings/SettingTypes/LocalNumberSetting.cs index fb86fdc..f37ea80 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; @@ -34,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. /// @@ -64,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); @@ -73,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); } @@ -135,11 +142,21 @@ public class LocalNumberSetting : LocalSettingBase return button.gameObject; } + /// + public override void RefreshOption() + { + _btnText.text = GetValueText(); + if (_highlight) + { + _highlight.gameObject.SetActive(false); + } + } + /// protected override string GetValueText() { var value = GetValue(); var formated = Helpers.FormatValue(value, SuffixType, FormatString); - return $"{Name}: {formated}"; + return $"{Name.Translate()}: {formated}"; } } 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 ae6f500..2a90bfd 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; @@ -39,6 +40,8 @@ public class LocalSliderSetting : LocalSettingBase /// public MiraNumberSuffixes SuffixType { get; } + private SlideBar _slider { get; set; } + /// /// Initializes a new instance of the class. /// @@ -70,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(); @@ -104,6 +108,12 @@ public class LocalSliderSetting : LocalSettingBase return newSlider.gameObject; } + /// + public override void RefreshOption() + { + _slider.Title.text = GetValueText(); + } + /// protected override string GetValueText() { @@ -112,9 +122,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..f28b6db 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; @@ -14,6 +15,8 @@ namespace MiraAPI.LocalSettings.SettingTypes; /// public class LocalToggleSetting : LocalSettingBase { + private ToggleButtonBehaviour _toggle { get; set; } + /// /// Initializes a new instance of the class. /// @@ -31,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(); @@ -46,9 +50,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 = MiraLocaleManager.GetOrCreateLocaleString(Name); 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; @@ -85,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/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index 36b1747..ef9b2b9 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -1,12 +1,15 @@ global using static Reactor.Utilities.Logger; using System; +using System.Globalization; using BepInEx; using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using HarmonyLib; using MiraAPI.PluginLoading; +using MiraAPI.Translation; using MiraAPI.VanillaEvents; using Reactor; +using Reactor.Localization; using Reactor.Networking; using Reactor.Networking.Attributes; using Reactor.Utilities; @@ -29,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"; @@ -69,6 +77,9 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin PluginManager = new MiraPluginManager(); PluginManager.Initialize(this, this); + MiraLocaleManager.Register("mira.api"); + LocalizationManager.Register(new MiraLocalizationProvider()); + IL2CPPChainloader.Instance.Finished += ModCompatibility .Initialize; // Initialise AFTER the mods are loaded to ensure maximum parity (no need for the soft dependency either then) diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index d9e6785..9999f15 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -4,6 +4,7 @@ using MiraAPI.Hud; using MiraAPI.LocalSettings; using MiraAPI.LocalSettings.Attributes; using MiraAPI.Patches; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using UnityEngine; @@ -16,7 +17,7 @@ namespace MiraAPI; public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) { /// - public override string TabName => "Mira API"; + public override string TabName => "MiraApi"; /// public override LocalSettingTabAppearance TabAppearance => new() @@ -204,58 +205,52 @@ 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); - - /// - /// Gets whether to apply cosmetic changes to the TaskAdder. - /// - [LocalToggleSetting] - public ConfigEntry PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", 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("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/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..71fa411 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -5,11 +5,11 @@ 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; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; @@ -268,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(); @@ -354,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) @@ -409,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) { @@ -458,7 +448,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/GameModes/HudPatches.cs b/MiraAPI/Patches/GameModes/HudPatches.cs index 735d198..4426e0a 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("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/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index fecc455..3a7f235 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -7,6 +7,7 @@ using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.MeetingAbilities; using MiraAPI.GameModes; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; namespace MiraAPI.Patches; @@ -86,8 +87,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/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index a8cab15..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); @@ -213,6 +214,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/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 ba3d268..46108fc 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -9,6 +9,7 @@ using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Presets; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -113,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) @@ -456,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) @@ -538,7 +539,7 @@ internal static class GameOptionsMenuPatch internal 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; } @@ -696,7 +697,7 @@ internal static class GameOptionsMenuPatch Quaternion.identity, container); - 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) @@ -715,7 +716,7 @@ internal static class GameOptionsMenuPatch categoryHeaderMasked.gameObject.SetActive(false); var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); - newText.text = "(Click to close)"; + newText.text = $"({"MiraApi.GameSetting.Global.ClickToClose".Translate()})"; newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); newText.gameObject.GetComponent().Destroy(); @@ -732,8 +733,8 @@ internal static class GameOptionsMenuPatch { group.AllOptionsHidden = !group.AllOptionsHidden; newText.text = group.AllOptionsHidden - ? "(Click to open)" - : "(Click to close)"; + ? $"({"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 bc6dc3a..c1db00b 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 = "MiraApi.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 = "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 a048c33..3435277 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -8,6 +8,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; @@ -31,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() { @@ -83,7 +83,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 = "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 ? "Main" : cgm.ColoredName; + menu.gameModeText.text = cgm is null ? MiraLocaleManager.Get("MiraApi.Gamemode.Classic") : cgm.ColoredName; } else { @@ -362,7 +362,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); @@ -454,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) { @@ -465,12 +465,7 @@ public static class LobbyViewPanePatches var group = grouping.Key; - var name = group.Name switch - { - "Crewmate" => StringNames.CrewmateRolesHeader, - "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(group.Name), - }; + 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/MenuState.cs b/MiraAPI/Patches/Options/MenuState.cs index b52bd95..e21e364 100644 --- a/MiraAPI/Patches/Options/MenuState.cs +++ b/MiraAPI/Patches/Options/MenuState.cs @@ -7,6 +7,7 @@ using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Presets; +using MiraAPI.Translation; using MiraAPI.Roles; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Attributes; @@ -149,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 = "Modifiers"; + mBtn.buttonText.text = "MiraApi.GameSetting.Modifiers".Translate(); mBtn.OnClick = new Button.ButtonClickedEvent(); mBtn.OnClick.AddListener( (UnityAction)(() => @@ -218,7 +219,7 @@ public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) })); var roleText = smRolesBtn.buttonText; - roleText.text = "Roles"; + roleText.text = "MiraApi.GameSetting.Roles".Translate(); roleText.GetComponent().Destroy(); roleText.alignment = TextAlignmentOptions.Center; roleText.transform.parent.localPosition = new Vector3( @@ -243,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 = "Modifiers"; + smModBtn.buttonText.text = "MiraApi.GameSetting.Modifiers".Translate(); smModBtn.OnClick = new Button.ButtonClickedEvent(); smModBtn.OnClick.AddListener( (UnityAction)(() => @@ -388,7 +389,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)]; } @@ -492,12 +493,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; } @@ -506,7 +507,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 @@ -519,7 +520,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 @@ -573,15 +574,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 fbcbe38..f6b873c 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -3,6 +3,7 @@ using Il2CppSystem; using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; @@ -164,7 +165,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; } @@ -201,8 +202,7 @@ public static class OptionsPatches { return true; } - __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title); - + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title).Translate(); return false; } @@ -309,7 +309,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 c5e63be..390adfb 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -9,6 +9,7 @@ using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -195,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; @@ -214,12 +215,7 @@ public static class RoleSettingMenuPatches RoleGroupHidden.TryAdd(group, false); - var name = group.Name switch - { - "Crewmate" => StringNames.CrewmateRolesHeader, - "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(group.Name), - }; + var name = MiraLocaleManager.GetOrCreateLocaleString(group.Name); var categoryHeaderMasked = Object.Instantiate( template, @@ -293,8 +289,8 @@ public static class RoleSettingMenuPatches quotaInst.gameObject.SetActive(!RoleGroupHidden[group]); var label = RoleGroupHidden[group] - ? "(Click to open)" - : "(Click to close)"; + ? $"({"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); @@ -579,7 +575,7 @@ public static class RoleSettingMenuPatches return; } - __instance.roleDescriptionText.text = customRole.RoleLongDescription; + __instance.roleDescriptionText.text = customRole.RoleMedDescription; __instance.roleTitleText.text = role.GetRoleName(); var imgBg = __instance.AdvancedRolesSettings.transform.FindChild("Imagebackground"); @@ -614,7 +610,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/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/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index bf9e563..1a3fe4b 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -23,6 +23,7 @@ using MiraAPI.Modifiers; using MiraAPI.Patches.Options; using MiraAPI.Presets; using MiraAPI.Roles; +using MiraAPI.Translation; using MiraAPI.Utilities; using Reactor.Localization.Utilities; using Reactor.Networking; @@ -150,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; } @@ -271,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); @@ -577,7 +563,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/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml new file mode 100644 index 0000000..742db18 --- /dev/null +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -0,0 +1,42 @@ + + + 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! +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 + diff --git a/MiraAPI/Resources/Locale/zh_CN.xml b/MiraAPI/Resources/Locale/zh_CN.xml new file mode 100644 index 0000000..12af14c --- /dev/null +++ b/MiraAPI/Resources/Locale/zh_CN.xml @@ -0,0 +1,25 @@ + + + Mira API + 界面/视觉 + 无障碍 + MiraAPI + 在左侧显示附加职业UI + 在按钮上显示快捷键 + 按钮缩放比例 + 最大帧数 + 启用破坏红色闪烁 + 启用破坏音效 + 返回职业设置 + 自定义类别 1 + 自定义类别 2 + 在此配置附加职业及其设置! + 为此模组应用游戏设置! + 默认 + 默认原版游戏模式 + 点击关闭 + 为本游戏选择或创建一个预设 + 保存 + 职业 + 附加职业 + diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 0b2be26..bc93252 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,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); - roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription); - roleBehaviour.BlurbNameLong = CustomStringName.CreateAndRegister(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; diff --git a/MiraAPI/Roles/CustomRoleUtils.cs b/MiraAPI/Roles/CustomRoleUtils.cs index 659076a..d43acbe 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.GameModes; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index f3f13d1..8f62a7f 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; @@ -13,35 +14,70 @@ namespace MiraAPI.Roles; /// public interface ICustomRole : IOptionable { + /// + /// Gets the id part used to build the role's translation keys. + /// + string IdPart => GetType().Name; + /// /// Gets the name of the role. /// - string RoleName { get; } + 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 { get; } + 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 { get; } + 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. /// 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("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 46b6067..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("Crewmate", 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("Impostor", 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("Neutral Roles", Color.gray); + public static RoleOptionsGroup Neutral { get; } = new("MiraApi.RoleTeam.Neutral.Group", Color.gray); } diff --git a/MiraAPI/Translation/MiraLanguage.cs b/MiraAPI/Translation/MiraLanguage.cs new file mode 100644 index 0000000..d6d2624 --- /dev/null +++ b/MiraAPI/Translation/MiraLanguage.cs @@ -0,0 +1,30 @@ +namespace MiraAPI.Translation; + +/// +/// Supported languages for Mira translation system. +/// Maps to Among Us's SupportedLangs enum values. +/// +public enum MiraLanguage +{ + English, + Latam, + Brazilian, + Portuguese, + Korean, + Russian, + Dutch, + Filipino, + French, + German, + 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..a34f22f --- /dev/null +++ b/MiraAPI/Translation/MiraLocaleManager.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using MiraAPI.Utilities; +using Reactor.Localization.Utilities; +using UnityEngine; + +namespace MiraAPI.Translation; + +public static class MiraLocaleManager +{ + private const string LangDirectory = "mira_languages"; + + public static readonly Dictionary>> Locale = []; + + public static readonly Dictionary RegisteredStringNames = []; + internal static readonly Dictionary StringNamesLookup = []; + + 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, 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); + } + + /// + /// 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); + } + + 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 internalName, string modGuid, string dir) + { + Directory.CreateDirectory(dir); + + if (!Locale.ContainsKey(modGuid)) + { + Locale[modGuid] = []; + } + + var resourcePrefix = $"{internalName}.Resources.Locale."; + + var atLeastOneLoaded = false; + foreach (var locale in LangList) + { + using var resourceStream = + assembly.GetManifestResourceStream(resourcePrefix + locale.Value + ".xml"); + 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.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)"); + atLeastOneLoaded = true; + } + catch (Exception e) + { + Error($"Failed to load translation {resourcePrefix}{locale.Value}: {e.Message}"); + } + } + + if (!atLeastOneLoaded) + { + Error($"No internal strings were found for {internalName}!"); + } + + if (!Locale.TryGetValue(modGuid, out _)) + { + Locale[modGuid] = []; + } + + foreach (var filePath in Directory.GetFiles(dir, "*")) + { + var fileName = Path.GetFileNameWithoutExtension(filePath); + + var lang = LangList.ContainsValue(fileName) + ? LangList.First(x => x.Value == fileName).Key + : (MiraLanguage)(-1); + if ((int)lang == -1) continue; + + try + { + var dict = ParseXmlFile(filePath); + Locale[modGuid][lang] = dict; + Info($"Loaded external {lang.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)"); + } + catch (Exception e) + { + Error($"Failed to load external translation {filePath}: {e.Message}"); + } + } + } + + private static Dictionary ParseXmlFile(string xmlContent) + { + var dict = new Dictionary(); + XmlDocument xmlDoc = new(); + try + { + xmlDoc.LoadXml(xmlContent); + XmlNodeList? stringNodes = xmlDoc.SelectNodes("/resources/string"); + + if (stringNodes != null) + { + 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; + } + } + } + } + catch (XmlException ex) + { + Error($"XML parsing error: {ex.Message}"); + } + + return dict; + } +} diff --git a/MiraAPI/Translation/MiraLocalizationProvider.cs b/MiraAPI/Translation/MiraLocalizationProvider.cs new file mode 100644 index 0000000..47cf747 --- /dev/null +++ b/MiraAPI/Translation/MiraLocalizationProvider.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using MiraAPI.LocalSettings; +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 (MiraLocaleManager.StringNamesLookup.TryGetValue(stringName, out var key)) + { + result = MiraLocaleManager.Get(key); + 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); + } + + 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) + { + var value = stringName.Value.Replace("\n", "\\%nl\\%"); + value = value.Replace("{", "["); + value = value.Replace("}", "]"); + Warning($"{value}"); + } + Warning($"");*/ + } +} diff --git a/MiraAPI/Translation/TranslationExtensions.cs b/MiraAPI/Translation/TranslationExtensions.cs new file mode 100644 index 0000000..5f0edbd --- /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 MiraLocaleManager.Get(key); + } +} 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. diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 9f66817..687298e 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;