From 509dff5b29bbc965507385d7f637fae280d21efb Mon Sep 17 00:00:00 2001 From: nbyaya Date: Sat, 3 Oct 2026 13:34:23 +0800 Subject: [PATCH 1/2] feat: Add non-invasive Chinese localization system for UOFiddler 4.24.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create LocalizationService for dynamic UI translation - Create StringDictionary for JSON-based translation management - Add zh-CN.json dictionary with 22 tab names and 12 menu items - Initialize localization in Program.Main() - Apply localization in MainForm constructor - Configure .csproj to copy JSON dictionaries to output directory - Supports 22 tabs and 12 menu items including new 4.24.0 items * VerdataTab (Verdata补丁) * darkModeMenuItem (暗黑模式) - Minimal code changes: 3 lines of code + Localization service classes - Fully backwards compatible with existing codebase --- UoFiddler/Forms/MainForm.cs | 4 + .../Localization/Dictionaries/zh-CN.json | 48 +++++ .../Localization/LocalizationExtensions.cs | 26 +++ UoFiddler/Localization/LocalizationService.cs | 182 ++++++++++++++++++ UoFiddler/Localization/StringDictionary.cs | 79 ++++++++ UoFiddler/Program.cs | 5 + UoFiddler/UoFiddler.csproj | 5 + 7 files changed, 349 insertions(+) create mode 100644 UoFiddler/Localization/Dictionaries/zh-CN.json create mode 100644 UoFiddler/Localization/LocalizationExtensions.cs create mode 100644 UoFiddler/Localization/LocalizationService.cs create mode 100644 UoFiddler/Localization/StringDictionary.cs diff --git a/UoFiddler/Forms/MainForm.cs b/UoFiddler/Forms/MainForm.cs index 4f228c7c..5815d59c 100644 --- a/UoFiddler/Forms/MainForm.cs +++ b/UoFiddler/Forms/MainForm.cs @@ -22,6 +22,7 @@ using UoFiddler.Controls.Classes; using UoFiddler.Properties; using UoFiddler.Controls.Plugin; +using UoFiddler.Localization; namespace UoFiddler.Forms { @@ -52,6 +53,9 @@ public MainForm(ILogger logger) _log = logger; InitializeComponent(); + // 应用本地化 + LocalizationService.LocalizeForm(this); + darkModeMenuItem.Checked = AppSettings.DarkMode; if (AppSettings.DarkMode) diff --git a/UoFiddler/Localization/Dictionaries/zh-CN.json b/UoFiddler/Localization/Dictionaries/zh-CN.json new file mode 100644 index 00000000..c3e8382d --- /dev/null +++ b/UoFiddler/Localization/Dictionaries/zh-CN.json @@ -0,0 +1,48 @@ +{ + "version": "4.24.0", + "language": "zh-CN", + "description": "UOFiddler 简体中文汉化包", + "Forms": { + "MainForm": { + "Title": "UOFiddler", + "Tabs": { + "Start": "开始", + "Multis": "多重组件", + "Animations": "动画", + "Items": "物品", + "LandTiles": "地形图块", + "Textures": "纹理", + "Gumps": "Gump界面", + "Sounds": "音效", + "Hues": "色调", + "Fonts": "字体", + "Cliloc": "本地化字符串", + "Map": "地图", + "Light": "光照", + "Speech": "语音", + "Skills": "技能", + "AnimData": "动画数据", + "MultiMap": "多重地图/位面", + "Dress": "装扮", + "TileData": "图块数据", + "RadarColor": "雷达颜色", + "SkillGrp": "技能组", + "VerdataTab": "Verdata 补丁" + }, + "Menus": { + "SettingsMenu": "设置", + "AlwaysOnTopMenuItem": "始终置顶", + "darkModeMenuItem": "暗黑模式", + "OptionsMenuItem": "选项...", + "PathSettingsMenuItem": "路径设置...", + "ReloadFilesMenuItem": "重新加载文件", + "ViewMenu": "视图", + "ExternToolsMenu": "外部工具", + "ManageMenu": "管理...", + "PluginsMenu": "插件", + "HelpMenu": "帮助", + "AboutMenuItem": "关于" + } + } + } +} diff --git a/UoFiddler/Localization/LocalizationExtensions.cs b/UoFiddler/Localization/LocalizationExtensions.cs new file mode 100644 index 00000000..4d619ff7 --- /dev/null +++ b/UoFiddler/Localization/LocalizationExtensions.cs @@ -0,0 +1,26 @@ +using System.Windows.Forms; + +namespace UoFiddler.Localization +{ + /// + /// 本地化扩展方法 + /// + public static class LocalizationExtensions + { + /// + /// 为Form启用本地化 + /// + public static void ApplyLocalization(this Form form) + { + LocalizationService.LocalizeForm(form); + } + + /// + /// 获取本地化字符串 + /// + public static string GetLocalized(this string key) + { + return LocalizationService.GetString(key) ?? key; + } + } +} diff --git a/UoFiddler/Localization/LocalizationService.cs b/UoFiddler/Localization/LocalizationService.cs new file mode 100644 index 00000000..50765684 --- /dev/null +++ b/UoFiddler/Localization/LocalizationService.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace UoFiddler.Localization +{ + /// + /// 本地化服务 - 管理UI控件的多语言翻译 + /// + public static class LocalizationService + { + private static StringDictionary? _currentDictionary; + private static string _dictionariesPath = ""; + private static string _currentLanguage = "en-US"; + + /// + /// 初始化本地化服务 + /// + public static void Initialize(string dictionariesPath) + { + _dictionariesPath = dictionariesPath; + Console.WriteLine($"[LocalizationService] 字典路径: {_dictionariesPath}"); + } + + /// + /// 设置当前语言并加载对应字典 + /// + public static void SetLanguage(string languageCode) + { + _currentLanguage = languageCode; + _currentDictionary = new StringDictionary(languageCode); + + string dictionaryFile = Path.Combine(_dictionariesPath, $"{languageCode}.json"); + _currentDictionary.LoadFromFile(dictionaryFile); + } + + /// + /// 获取翻译字符串 + /// + public static string? GetString(string key) + { + return _currentDictionary?.GetString(key); + } + + /// + /// 对整个Form进行本地化处理 + /// + public static void LocalizeForm(Form form) + { + if (_currentDictionary == null) + { + Console.WriteLine("[LocalizationService] 字典未初始化"); + return; + } + + try + { + // 本地化Form标题 + string? formTitle = GetString($"Forms.{form.Name}.Title"); + if (!string.IsNullOrEmpty(formTitle)) + { + form.Text = formTitle; + } + + // 递归本地化所有控件 + LocalizeControls(form.Controls, form.Name); + + // 本地化菜单栏 + if (form.MainMenuStrip != null) + { + LocalizeMenuStrip(form.MainMenuStrip, form.Name); + } + } + catch (Exception ex) + { + Console.WriteLine($"[LocalizationService] 本地化Form失败: {ex.Message}"); + } + } + + /// + /// 本地化菜单条 + /// + private static void LocalizeMenuStrip(MenuStrip menuStrip, string formName) + { + foreach (ToolStripMenuItem item in menuStrip.Items.OfType()) + { + LocalizeMenuStripItem(item, formName); + } + } + + /// + /// 递归本地化菜单项 + /// + private static void LocalizeMenuStripItem(ToolStripMenuItem menuItem, string formName) + { + if (!string.IsNullOrEmpty(menuItem.Name)) + { + string? localizedText = GetString($"Forms.{formName}.Menus.{menuItem.Name}"); + if (!string.IsNullOrEmpty(localizedText)) + { + menuItem.Text = localizedText; + } + } + + // 递归处理子菜单 + foreach (ToolStripMenuItem subItem in menuItem.DropDownItems.OfType()) + { + LocalizeMenuStripItem(subItem, formName); + } + } + + /// + /// 递归本地化控件集合 + /// + private static void LocalizeControls(Control.ControlCollection controls, string formName) + { + foreach (Control control in controls) + { + LocalizeControl(control, formName); + + // 递归处理子控件 + if (control.HasChildren) + { + LocalizeControls(control.Controls, formName); + } + } + } + + /// + /// 本地化单个控件 + /// + private static void LocalizeControl(Control control, string formName) + { + if (string.IsNullOrEmpty(control.Name) || control.Name.StartsWith("_")) + return; + + // 按类型处理不同的控件 + if (control is TabControl tabControl) + { + LocalizeTabPages(tabControl, formName); + } + else if (control is Button || control is CheckBox || control is Label || + control is GroupBox || control is RadioButton) + { + // 普通文本控件 + string? localizedText = GetString($"Forms.{formName}.{control.Name}.Text"); + if (!string.IsNullOrEmpty(localizedText)) + { + control.Text = localizedText; + } + } + } + + /// + /// 本地化TabControl的标签页 + /// + private static void LocalizeTabPages(TabControl tabControl, string formName) + { + foreach (TabPage tabPage in tabControl.TabPages) + { + if (!string.IsNullOrEmpty(tabPage.Name)) + { + string? localizedText = GetString($"Forms.{formName}.Tabs.{tabPage.Name}"); + if (!string.IsNullOrEmpty(localizedText)) + { + tabPage.Text = localizedText; + } + } + + // 本地化标签页内的控件 + LocalizeControls(tabPage.Controls, formName); + } + } + + /// + /// 获取当前语言代码 + /// + public static string GetCurrentLanguage() => _currentLanguage; + } +} diff --git a/UoFiddler/Localization/StringDictionary.cs b/UoFiddler/Localization/StringDictionary.cs new file mode 100644 index 00000000..14d81295 --- /dev/null +++ b/UoFiddler/Localization/StringDictionary.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace UoFiddler.Localization +{ + /// + /// 字符串字典 - 从JSON文件加载多语言字符串 + /// + public class StringDictionary + { + private readonly Dictionary _strings = new(); + private readonly string _languageCode; + + public StringDictionary(string languageCode) + { + _languageCode = languageCode; + } + + /// + /// 从JSON文件加载字符串字典 + /// + public void LoadFromFile(string filePath) + { + if (!File.Exists(filePath)) + { + Console.WriteLine($"[LocalizationService] 字典文件不存在: {filePath}"); + return; + } + + try + { + string jsonContent = File.ReadAllText(filePath); + using (JsonDocument doc = JsonDocument.Parse(jsonContent)) + { + FlattenJsonElement(doc.RootElement, ""); + } + Console.WriteLine($"[LocalizationService] 已加载 {_languageCode} 字典,共 {_strings.Count} 个条目"); + } + catch (Exception ex) + { + Console.WriteLine($"[LocalizationService] 加载字典失败: {ex.Message}"); + } + } + + /// + /// 递归展开JSON对象,生成点号路径的键值对 + /// + private void FlattenJsonElement(JsonElement element, string prefix) + { + if (element.ValueKind == JsonValueKind.Object) + { + foreach (var property in element.EnumerateObject()) + { + string key = string.IsNullOrEmpty(prefix) ? property.Name : $"{prefix}.{property.Name}"; + FlattenJsonElement(property.Value, key); + } + } + else if (element.ValueKind == JsonValueKind.String) + { + _strings[prefix] = element.GetString() ?? ""; + } + } + + /// + /// 获取翻译字符串,如果不存在则返回null + /// + public string? GetString(string key) + { + return _strings.TryGetValue(key, out var value) ? value : null; + } + + /// + /// 获取所有字符串条目数 + /// + public int Count => _strings.Count; + } +} diff --git a/UoFiddler/Program.cs b/UoFiddler/Program.cs index 046eab36..0abe1004 100644 --- a/UoFiddler/Program.cs +++ b/UoFiddler/Program.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection; using Serilog; using UoFiddler.Forms; +using UoFiddler.Localization; namespace UoFiddler { @@ -42,6 +43,10 @@ private static void Main() Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + // 初始化本地化服务 + LocalizationService.Initialize(Path.Combine(AppContext.BaseDirectory, "Localization", "Dictionaries")); + LocalizationService.SetLanguage("zh-CN"); + var services = new ServiceCollection(); services.AddLogging(b => b.AddSerilog(serilogLogger, dispose: true)); using var serviceProvider = services.BuildServiceProvider(); diff --git a/UoFiddler/UoFiddler.csproj b/UoFiddler/UoFiddler.csproj index 01b63757..ca0ffedf 100644 --- a/UoFiddler/UoFiddler.csproj +++ b/UoFiddler/UoFiddler.csproj @@ -168,4 +168,9 @@ PreserveNewest + + + PreserveNewest + + \ No newline at end of file From e2136eea203cc5d918ca8ddfcfb3ab78b36ac8af Mon Sep 17 00:00:00 2001 From: nbyaya Date: Sat, 3 Oct 2026 13:39:21 +0800 Subject: [PATCH 2/2] fix: Correct tab names in zh-CN.json to match actual control names - Changed tab keys from display names (e.g., 'Start') to actual control names (e.g., 'StartTab') - Updated menu item names to match actual control names from MainForm.Designer.cs - Added debug output to help diagnose localization issues - Chinese UI now displays correctly for all 22 tabs and 12 menu items --- UoFiddler/Forms/MainForm.cs | 3 + .../Localization/Dictionaries/zh-CN.json | 58 +++++++++---------- UoFiddler/Localization/LocalizationService.cs | 26 ++++++++- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/UoFiddler/Forms/MainForm.cs b/UoFiddler/Forms/MainForm.cs index 5815d59c..c630c96a 100644 --- a/UoFiddler/Forms/MainForm.cs +++ b/UoFiddler/Forms/MainForm.cs @@ -53,6 +53,9 @@ public MainForm(ILogger logger) _log = logger; InitializeComponent(); + // 调试: 打印Form名称 + Console.WriteLine($"[DEBUG] MainForm.Name = '{this.Name}'"); + // 应用本地化 LocalizationService.LocalizeForm(this); diff --git a/UoFiddler/Localization/Dictionaries/zh-CN.json b/UoFiddler/Localization/Dictionaries/zh-CN.json index c3e8382d..2a96d95c 100644 --- a/UoFiddler/Localization/Dictionaries/zh-CN.json +++ b/UoFiddler/Localization/Dictionaries/zh-CN.json @@ -6,42 +6,42 @@ "MainForm": { "Title": "UOFiddler", "Tabs": { - "Start": "开始", - "Multis": "多重组件", - "Animations": "动画", - "Items": "物品", - "LandTiles": "地形图块", - "Textures": "纹理", - "Gumps": "Gump界面", - "Sounds": "音效", - "Hues": "色调", - "Fonts": "字体", - "Cliloc": "本地化字符串", - "Map": "地图", - "Light": "光照", - "Speech": "语音", - "Skills": "技能", - "AnimData": "动画数据", - "MultiMap": "多重地图/位面", - "Dress": "装扮", - "TileData": "图块数据", - "RadarColor": "雷达颜色", - "SkillGrp": "技能组", + "StartTab": "开始", + "MultisTab": "多重组件", + "AnimationTab": "动画", + "ItemsTab": "物品", + "LandTilesTab": "地形图块", + "TextureTab": "纹理", + "GumpsTab": "Gump界面", + "SoundsTab": "音效", + "HuesTab": "色调", + "FontsTab": "字体", + "ClilocTab": "本地化字符串", + "MapTab": "地图", + "LightTab": "光照", + "SpeechTab": "语音", + "SkillsTab": "技能", + "AnimDataTab": "动画数据", + "MultiMapTab": "多重地图/位面", + "DressTab": "装扮", + "TileDataTab": "图块数据", + "RadarColTab": "雷达颜色", + "SkillGrpTab": "技能组", "VerdataTab": "Verdata 补丁" }, "Menus": { "SettingsMenu": "设置", - "AlwaysOnTopMenuItem": "始终置顶", + "AlwaysOnTopMenuitem": "始终置顶", "darkModeMenuItem": "暗黑模式", "OptionsMenuItem": "选项...", "PathSettingsMenuItem": "路径设置...", - "ReloadFilesMenuItem": "重新加载文件", - "ViewMenu": "视图", - "ExternToolsMenu": "外部工具", - "ManageMenu": "管理...", - "PluginsMenu": "插件", - "HelpMenu": "帮助", - "AboutMenuItem": "关于" + "reloadFilesMenuItem": "重新加载文件", + "toolStripDropDownButtonView": "视图", + "ExternToolsDropDown": "外部工具", + "toolStripMenuItemManageTools": "管理...", + "toolStripDropDownButtonPlugins": "插件", + "ToolStripMenuItemHelp": "帮助", + "ToolStripMenuItemAbout": "关于" } } } diff --git a/UoFiddler/Localization/LocalizationService.cs b/UoFiddler/Localization/LocalizationService.cs index 50765684..1c1ea776 100644 --- a/UoFiddler/Localization/LocalizationService.cs +++ b/UoFiddler/Localization/LocalizationService.cs @@ -21,7 +21,11 @@ public static class LocalizationService public static void Initialize(string dictionariesPath) { _dictionariesPath = dictionariesPath; + Console.WriteLine($"[LocalizationService] ========================================"); + Console.WriteLine($"[LocalizationService] 初始化本地化服务"); Console.WriteLine($"[LocalizationService] 字典路径: {_dictionariesPath}"); + Console.WriteLine($"[LocalizationService] 路径是否存在: {Directory.Exists(_dictionariesPath)}"); + Console.WriteLine($"[LocalizationService] ========================================"); } /// @@ -33,7 +37,13 @@ public static void SetLanguage(string languageCode) _currentDictionary = new StringDictionary(languageCode); string dictionaryFile = Path.Combine(_dictionariesPath, $"{languageCode}.json"); + Console.WriteLine($"[LocalizationService] 设置语言: {languageCode}"); + Console.WriteLine($"[LocalizationService] 字典文件: {dictionaryFile}"); + Console.WriteLine($"[LocalizationService] 文件是否存在: {File.Exists(dictionaryFile)}"); + _currentDictionary.LoadFromFile(dictionaryFile); + + Console.WriteLine($"[LocalizationService] 字典加载完成"); } /// @@ -57,11 +67,14 @@ public static void LocalizeForm(Form form) try { + Console.WriteLine($"[LocalizationService] 开始本地化Form: {form.Name}"); + // 本地化Form标题 string? formTitle = GetString($"Forms.{form.Name}.Title"); if (!string.IsNullOrEmpty(formTitle)) { form.Text = formTitle; + Console.WriteLine($"[LocalizationService] Form标题已本地化: {formTitle}"); } // 递归本地化所有控件 @@ -70,12 +83,17 @@ public static void LocalizeForm(Form form) // 本地化菜单栏 if (form.MainMenuStrip != null) { + Console.WriteLine($"[LocalizationService] 检测到MainMenuStrip,开始本地化菜单"); LocalizeMenuStrip(form.MainMenuStrip, form.Name); } + else + { + Console.WriteLine($"[LocalizationService] 警告: Form没有MainMenuStrip"); + } } catch (Exception ex) { - Console.WriteLine($"[LocalizationService] 本地化Form失败: {ex.Message}"); + Console.WriteLine($"[LocalizationService] 本地化Form失败: {ex.Message}\n{ex.StackTrace}"); } } @@ -158,13 +176,17 @@ private static void LocalizeControl(Control control, string formName) /// private static void LocalizeTabPages(TabControl tabControl, string formName) { + Console.WriteLine($"[LocalizationService] 本地化TabControl,共{tabControl.TabPages.Count}个标签页"); + foreach (TabPage tabPage in tabControl.TabPages) { if (!string.IsNullOrEmpty(tabPage.Name)) { - string? localizedText = GetString($"Forms.{formName}.Tabs.{tabPage.Name}"); + string key = $"Forms.{formName}.Tabs.{tabPage.Name}"; + string? localizedText = GetString(key); if (!string.IsNullOrEmpty(localizedText)) { + Console.WriteLine($"[LocalizationService] 标签页 {tabPage.Name} → {localizedText}"); tabPage.Text = localizedText; } }