From 07b8cc04be7495e40e3ca5cf7c3a81365c1c3aab Mon Sep 17 00:00:00 2001 From: Clansty Date: Mon, 30 Sep 2024 01:26:54 +0800 Subject: [PATCH] [+] i18n --- AquaMai/AquaMai.csproj | 13 ++ AquaMai/Config.cs | 1 + AquaMai/Fix/I18nSingleAssemblyHook.cs | 33 +++++ AquaMai/Main.cs | 25 ++++ AquaMai/Resources/Locale.Designer.cs | 181 ++++++++++++++++++++++++++ AquaMai/Resources/Locale.resx | 60 +++++++++ AquaMai/Resources/Locale.zh.resx | 53 ++++++++ AquaMai/Utils/PractiseModeUI.cs | 29 +++-- 8 files changed, 381 insertions(+), 14 deletions(-) create mode 100644 AquaMai/Fix/I18nSingleAssemblyHook.cs create mode 100644 AquaMai/Resources/Locale.Designer.cs create mode 100644 AquaMai/Resources/Locale.resx create mode 100644 AquaMai/Resources/Locale.zh.resx diff --git a/AquaMai/AquaMai.csproj b/AquaMai/AquaMai.csproj index 120ca7be..0f42a5de 100644 --- a/AquaMai/AquaMai.csproj +++ b/AquaMai/AquaMai.csproj @@ -299,6 +299,7 @@ DEBUG + @@ -307,6 +308,11 @@ DEBUG + + True + True + Locale.resx + @@ -344,6 +350,13 @@ DEBUG + + ResXFileCodeGenerator + Locale.Designer.cs + + + Locale.resx + diff --git a/AquaMai/Config.cs b/AquaMai/Config.cs index 7ea65902..e89d6011 100644 --- a/AquaMai/Config.cs +++ b/AquaMai/Config.cs @@ -22,6 +22,7 @@ namespace AquaMai public class UXConfig { + public string Locale { get; set; } public bool SinglePlayer { get; set; } public bool LoadAssetsPng { get; set; } public bool LoadJacketPng { get; set; } diff --git a/AquaMai/Fix/I18nSingleAssemblyHook.cs b/AquaMai/Fix/I18nSingleAssemblyHook.cs new file mode 100644 index 00000000..aebae280 --- /dev/null +++ b/AquaMai/Fix/I18nSingleAssemblyHook.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using System.Resources; +using HarmonyLib; +using MelonLoader; + +namespace AquaMai.Fix; + +public class I18nSingleAssemblyHook +{ + [HarmonyPatch(typeof(ResourceManager), "InternalGetResourceSet", typeof(CultureInfo), typeof(bool), typeof(bool))] + [HarmonyPrefix] + public static bool GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents, ref ResourceSet __result, ResourceManager __instance) + { + var GetResourceFileName = __instance.GetType().GetMethod("GetResourceFileName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var resourceFileName = (string)GetResourceFileName.Invoke(__instance, [culture]); + var MainAssembly = typeof(AquaMai).Assembly; + var manifestResourceStream = MainAssembly.GetManifestResourceStream(resourceFileName); + if (manifestResourceStream == null) + { + return true; + } + + var resourceGroveler = __instance.GetType().GetField("resourceGroveler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance); + var CreateResourceSet = resourceGroveler.GetType().GetMethod("CreateResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var resourceSet = CreateResourceSet.Invoke(resourceGroveler, [manifestResourceStream, MainAssembly]); + var AddResourceSet = __instance.GetType().GetMethod("AddResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var localResourceSets = __instance.GetType().GetField("_resourceSets", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance); + object[] args = [localResourceSets, culture.Name, resourceSet]; + AddResourceSet.Invoke(null, args); + __result = (ResourceSet)args[2]; + return false; + } +} diff --git a/AquaMai/Main.cs b/AquaMai/Main.cs index e79a6fe2..af155b39 100644 --- a/AquaMai/Main.cs +++ b/AquaMai/Main.cs @@ -1,13 +1,17 @@ using System; +using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading; using AquaMai.Fix; using AquaMai.Helpers; +using AquaMai.Resources; using AquaMai.Utils; using AquaMai.UX; using MelonLoader; using Tomlet; +using UnityEngine; namespace AquaMai { @@ -91,6 +95,22 @@ namespace AquaMai s.CopyTo(fs); } + private static void InitLocale() + { + if (!string.IsNullOrEmpty(AppConfig.UX.Locale)) + { + Locale.Culture = CultureInfo.GetCultureInfo(AppConfig.UX.Locale); + return; + } + + Locale.Culture = Application.systemLanguage switch + { + SystemLanguage.Chinese or SystemLanguage.ChineseSimplified or SystemLanguage.ChineseTraditional => CultureInfo.GetCultureInfo("zh"), + SystemLanguage.English => CultureInfo.GetCultureInfo("en"), + _ => CultureInfo.InvariantCulture + }; + } + public override void OnInitializeMelon() { // Prevent Chinese characters from being garbled @@ -119,6 +139,11 @@ namespace AquaMai AppConfig.UX.LoadAssetsPng = AppConfig.UX.LoadAssetsPng || AppConfig.UX.LoadJacketPng; AppConfig.UX.LoadJacketPng = false; + // Init locale with patching C# runtime + // https://stackoverflow.com/questions/1952638/single-assembly-multi-language-windows-forms-deployment-ilmerge-and-satellite-a + Patch(typeof(I18nSingleAssemblyHook)); + InitLocale(); + // Fixes that does not have side effects // These don't need to be configurable diff --git a/AquaMai/Resources/Locale.Designer.cs b/AquaMai/Resources/Locale.Designer.cs new file mode 100644 index 00000000..0934b60f --- /dev/null +++ b/AquaMai/Resources/Locale.Designer.cs @@ -0,0 +1,181 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using MelonLoader; + +namespace AquaMai.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Locale { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Locale() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AquaMai.Resources.Locale", typeof(Locale).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to End. + /// + internal static string MarkRepeatEnd { + get { + return ResourceManager.GetString("MarkRepeatEnd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start. + /// + internal static string MarkRepeatStart { + get { + return ResourceManager.GetString("MarkRepeatStart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pause. + /// + internal static string Pause { + get { + return ResourceManager.GetString("Pause", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loop Not Set. + /// + internal static string RepeatNotSet { + get { + return ResourceManager.GetString("RepeatNotSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reset. + /// + internal static string RepeatReset { + get { + return ResourceManager.GetString("RepeatReset", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loop Set. + /// + internal static string RepeatStartEndSet { + get { + return ResourceManager.GetString("RepeatStartEndSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loop Start Set. + /// + internal static string RepeatStartSet { + get { + return ResourceManager.GetString("RepeatStartSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Seek <<. + /// + internal static string SeekBackward { + get { + return ResourceManager.GetString("SeekBackward", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Seek >>. + /// + internal static string SeekForward { + get { + return ResourceManager.GetString("SeekForward", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Speed. + /// + internal static string Speed { + get { + return ResourceManager.GetString("Speed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Speed -. + /// + internal static string SpeedDown { + get { + return ResourceManager.GetString("SpeedDown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Speed Reset. + /// + internal static string SpeedReset { + get { + return ResourceManager.GetString("SpeedReset", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Speed +. + /// + internal static string SpeedUp { + get { + return ResourceManager.GetString("SpeedUp", resourceCulture); + } + } + } +} diff --git a/AquaMai/Resources/Locale.resx b/AquaMai/Resources/Locale.resx new file mode 100644 index 00000000..cfc74e0f --- /dev/null +++ b/AquaMai/Resources/Locale.resx @@ -0,0 +1,60 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Seek << + + + Seek >> + + + Pause + + + Start + + + End + + + Reset + + + Loop Not Set + + + Loop Start Set + + + Loop Set + + + Speed - + + + Speed + + + + Speed + + + Speed Reset + + diff --git a/AquaMai/Resources/Locale.zh.resx b/AquaMai/Resources/Locale.zh.resx new file mode 100644 index 00000000..7b17c872 --- /dev/null +++ b/AquaMai/Resources/Locale.zh.resx @@ -0,0 +1,53 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 倒退 << + + + 快进 >> + + + 暂停 + + + 标记结尾 + + + 标记开头 + + + 循环未设定 + + + 循环解除 + + + 循环已设定 + + + 循环开头已设定 + + + 速度 + + + 速度 - + + + 速度重置 + + + 速度 + + + diff --git a/AquaMai/Utils/PractiseModeUI.cs b/AquaMai/Utils/PractiseModeUI.cs index 79da940b..10f89a17 100644 --- a/AquaMai/Utils/PractiseModeUI.cs +++ b/AquaMai/Utils/PractiseModeUI.cs @@ -1,6 +1,7 @@ using System; using AquaMai.Fix; using AquaMai.Helpers; +using AquaMai.Resources; using Manager; using UnityEngine; @@ -55,31 +56,31 @@ public class PractiseModeUI : MonoBehaviour controlHeight * 4 + GuiSizes.Margin * 5 ), ""); - GUI.Button(GetButtonRect(0, 0), "Seek <<"); - GUI.Button(GetButtonRect(1, 0), "Pause"); - GUI.Button(GetButtonRect(2, 0), "Seek >>"); + GUI.Button(GetButtonRect(0, 0), Locale.SeekBackward); + GUI.Button(GetButtonRect(1, 0), Locale.Pause); + GUI.Button(GetButtonRect(2, 0), Locale.SeekForward); if (PractiseMode.repeatStart == -1) { - GUI.Button(GetButtonRect(0, 1), "Start"); - GUI.Label(GetButtonRect(1, 1), "Loop not set"); + GUI.Button(GetButtonRect(0, 1), Locale.MarkRepeatStart); + GUI.Label(GetButtonRect(1, 1), Locale.RepeatNotSet); } else if (PractiseMode.repeatEnd == -1) { - GUI.Button(GetButtonRect(0, 1), "End"); - GUI.Label(GetButtonRect(1, 1), "Loop start set"); - GUI.Button(GetButtonRect(2, 1), "Reset"); + GUI.Button(GetButtonRect(0, 1), Locale.MarkRepeatEnd); + GUI.Label(GetButtonRect(1, 1), Locale.RepeatStartSet); + GUI.Button(GetButtonRect(2, 1), Locale.RepeatReset); } else { - GUI.Label(GetButtonRect(1, 1), "Loop set"); - GUI.Button(GetButtonRect(2, 1), "Reset"); + GUI.Label(GetButtonRect(1, 1), Locale.RepeatStartEndSet); + GUI.Button(GetButtonRect(2, 1), Locale.RepeatReset); } - GUI.Button(GetButtonRect(0, 2), "Speed -"); - GUI.Label(GetButtonRect(1, 2), $"Speed {PractiseMode.speed * 100:000}%"); - GUI.Button(GetButtonRect(2, 2), "Speed +"); - GUI.Button(GetButtonRect(1, 3), "Speed Reset"); + GUI.Button(GetButtonRect(0, 2), Locale.SpeedDown); + GUI.Label(GetButtonRect(1, 2), $"{Locale.Speed} {PractiseMode.speed * 100:000}%"); + GUI.Button(GetButtonRect(2, 2), Locale.SpeedUp); + GUI.Button(GetButtonRect(1, 3), Locale.SpeedReset); } public void Update()