diff --git a/CFixer/CFixer.csproj b/CFixer/CFixer.csproj index 4648efc..cfba1fe 100644 --- a/CFixer/CFixer.csproj +++ b/CFixer/CFixer.csproj @@ -50,6 +50,7 @@ + ..\..\..\..\..\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Annotated\Windows.winmd @@ -88,7 +89,9 @@ + + Form @@ -207,6 +210,12 @@ + + PreserveNewest + + + PreserveNewest + diff --git a/CFixer/Features/FeatureManager.cs b/CFixer/Features/FeatureManager.cs index 2e9ac18..e187269 100644 --- a/CFixer/Features/FeatureManager.cs +++ b/CFixer/Features/FeatureManager.cs @@ -4,6 +4,7 @@ using System.Drawing; using System.Threading.Tasks; using System.Windows.Forms; +using CFixer; namespace CrapFixer { @@ -64,12 +65,17 @@ public static void LoadFeatures(TreeView tree) /// private static void AddNode(TreeNodeCollection treeNodes, FeatureNode featureNode) { + string localizedName = featureNode.IsCategory + ? LocalizationManager.LocalizeFeatureCategory(featureNode.Name) + : LocalizationManager.LocalizeFeatureName(featureNode); + string text = featureNode.IsCategory - ? " " + featureNode.Name + " " // add extra space to avoid clipping - : featureNode.Name; + ? " " + localizedName + " " // add extra space to avoid clipping + : localizedName; TreeNode node = new TreeNode(text) { + Name = featureNode.Name, Tag = featureNode, Checked = featureNode.DefaultChecked, }; @@ -93,7 +99,7 @@ public static async Task AnalyzeAll(TreeNodeCollection nodes) await AnalyzeCheckedRecursive(node); } - Logger.Log("🔎 ANALYSIS COMPLETE", LogLevel.Info); + Logger.Log("ANALYSIS COMPLETE", LogLevel.Info); Logger.Log(new string('=', 50), LogLevel.Info); int ok = totalChecked - issuesFound; @@ -119,8 +125,8 @@ private static async Task AnalyzeCheckedRecursive(TreeNode node) issuesFound++; node.ForeColor = Color.Red; // Mark as misconfigured string category = node.Parent?.Text ?? "General"; - Logger.Log($"❌ [{category}] {fn.Name} - Not configured as recommended."); - Logger.Log($" ➤ {fn.Feature.GetFeatureDetails()}"); + Logger.Log($"[{category}] {fn.Name} - Not configured as recommended."); + Logger.Log($" {fn.Feature.GetFeatureDetails()}"); // Log a separator when an issue was found Logger.Log(new string('-', 50), LogLevel.Info); } @@ -147,10 +153,11 @@ public static async Task FixChecked(TreeNode node) { if (!fn.IsCategory && node.Checked && fn.Feature != null) { + string displayName = LocalizationManager.LocalizeFeatureName(fn); bool result = await fn.Feature.DoFeature(); Logger.Log(result - ? $"🔧 {fn.Name} - Fixed" - : $"❌ {fn.Name} - ⚠️ Fix failed (This feature may require admin privileges)", + ? $"{displayName} - Fixed" + : $"{displayName} - Fix failed (This feature may require admin privileges)", result ? LogLevel.Info : LogLevel.Error); } @@ -168,11 +175,12 @@ public static void RestoreChecked(TreeNode node) { if (!fn.IsCategory && node.Checked && fn.Feature != null) { + string displayName = LocalizationManager.LocalizeFeatureName(fn); bool ok = fn.Feature.UndoFeature(); string category = node.Parent?.Text ?? "General"; Logger.Log(ok - ? $"↩️ [{category}] {fn.Name} - Restored" - : $"❌ [{category}] {fn.Name} - Restore failed", + ? $"[{category}] {displayName} - Restored" + : $"[{category}] {displayName} - Restore failed", ok ? LogLevel.Info : LogLevel.Error); } @@ -193,13 +201,13 @@ public static async void AnalyzeFeature(TreeNode node) if (isOk) { - Logger.Log($"✅ Feature: {fn.Name} is properly configured.", LogLevel.Info); + Logger.Log($"Feature: {fn.Name} is properly configured.", LogLevel.Info); } else { string category = node.Parent?.Text ?? "General"; - Logger.Log($"❌ Feature: {fn.Name} requires attention.", LogLevel.Warning); - Logger.Log($" ➤ {fn.Feature.GetFeatureDetails()}"); + Logger.Log($"Feature: {fn.Name} requires attention.", LogLevel.Warning); + Logger.Log($" {fn.Feature.GetFeatureDetails()}"); Logger.Log(new string('-', 50), LogLevel.Info); } } @@ -223,11 +231,12 @@ public static async Task FixFeature(TreeNode node) // Try to fix this node if it is NOT a category (i.e., a leaf node) if (node.Tag is FeatureNode fn && !fn.IsCategory && fn.Feature != null) { + string displayName = LocalizationManager.LocalizeFeatureName(fn); // Always fix the selected leaf node, regardless of Checked bool result = await fn.Feature.DoFeature(); Logger.Log(result - ? $"🔧 {fn.Name} - Fixed" - : $"❌ {fn.Name} - ⚠️ Fix failed (This feature may require admin privileges)", + ? $"{displayName} - Fixed" + : $"{displayName} - Fix failed (This feature may require admin privileges)", result ? LogLevel.Info : LogLevel.Error); } else @@ -251,10 +260,11 @@ public static void RestoreFeature(TreeNode node) // Restore feature node regardless of Checked state if (node.Tag is FeatureNode fn && !fn.IsCategory && fn.Feature != null) { + string displayName = LocalizationManager.LocalizeFeatureName(fn); bool ok = fn.Feature.UndoFeature(); Logger.Log(ok - ? $"↩️ {fn.Name} - Restored" - : $"❌ {fn.Name} - Restore failed", + ? $"{displayName} - Restored" + : $"{displayName} - Restore failed", ok ? LogLevel.Info : LogLevel.Error); } else @@ -278,10 +288,11 @@ public static void ShowHelp(TreeNode node) // Show help for features if (node?.Tag is FeatureNode fn && fn.Feature != null) { + string displayName = LocalizationManager.LocalizeFeatureName(fn); string info = fn.Feature.Info(); MessageBox.Show( !string.IsNullOrEmpty(info) ? info : "No additional information available.", - $"Help: {fn.Name}", + $"Help: {displayName}", MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -309,7 +320,7 @@ public static void ShowHelp(TreeNode node) // Show help for plugins if (!PluginManager.ShowHelp(node)) { - MessageBox.Show("⚠️ No feature or plugin selected, or help info unavailable.", + MessageBox.Show("No feature or plugin selected, or help info unavailable.", "Help", MessageBoxButtons.OK, MessageBoxIcon.Warning); diff --git a/CFixer/ILocalizedControl.cs b/CFixer/ILocalizedControl.cs new file mode 100644 index 0000000..17dbf78 --- /dev/null +++ b/CFixer/ILocalizedControl.cs @@ -0,0 +1,7 @@ +namespace CFixer +{ + internal interface ILocalizedControl + { + void RefreshLocalization(); + } +} diff --git a/CFixer/IniStateManager.cs b/CFixer/IniStateManager.cs index a53f864..8c22456 100644 --- a/CFixer/IniStateManager.cs +++ b/CFixer/IniStateManager.cs @@ -58,7 +58,8 @@ private static void AppendNodeStates(List lines, TreeNodeCollection node { foreach (TreeNode node in nodes) { - lines.Add($"{node.Text.Trim()}={node.Checked}"); + string key = string.IsNullOrWhiteSpace(node.Name) ? node.Text.Trim() : node.Name.Trim(); + lines.Add($"{key}={node.Checked}"); if (node.Nodes.Count > 0) AppendNodeStates(lines, node.Nodes); } @@ -175,9 +176,11 @@ private static void ApplyStates(TreeNodeCollection nodes, Dictionary 0) ApplyStates(node.Nodes, states); @@ -240,6 +243,65 @@ public static Dictionary LoadViewSettings(string viewName) return result; } + // Saves an individual string setting in a specific section. + public static void SaveViewStringSetting(string viewName, string key, string value) + { + lock (FileLock) + { + var lines = File.Exists(IniPath) ? File.ReadAllLines(IniPath).ToList() : new List(); + var sectionData = LoadViewStringSettingsInternal(lines, viewName); + sectionData[key] = value; + + RemoveSection(lines, viewName); + lines.Add($"[{viewName}]"); + foreach (var kvp in sectionData) + { + lines.Add($"{kvp.Key}={kvp.Value}"); + } + + File.WriteAllLines(IniPath, lines); + } + } + + // Loads one individual string setting from a section. + public static string LoadViewStringSetting(string viewName, string key, string defaultValue = "") + { + if (!File.Exists(IniPath)) return defaultValue; + + var lines = File.ReadAllLines(IniPath).ToList(); + var settings = LoadViewStringSettingsInternal(lines, viewName); + return settings.TryGetValue(key, out var value) ? value : defaultValue; + } + + private static Dictionary LoadViewStringSettingsInternal(List lines, string viewName) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + bool inTargetSection = false; + + foreach (var line in lines) + { + var trimmed = line.Trim(); + if (string.IsNullOrWhiteSpace(trimmed)) continue; + + if (trimmed.StartsWith("[") && trimmed.EndsWith("]")) + { + inTargetSection = trimmed.Equals($"[{viewName}]", StringComparison.OrdinalIgnoreCase); + continue; + } + + if (!inTargetSection) continue; + + var parts = trimmed.Split(new[] { '=' }, 2); + if (parts.Length != 2) continue; + + var settingKey = parts[0].Trim(); + var settingValue = parts[1].Trim(); + result[settingKey] = settingValue; + } + + return result; + } + // Checks if a setting exists in a specific section public static bool IsViewSettingEnabled(string sectionName, string settingName) { diff --git a/CFixer/LocalizationManager.cs b/CFixer/LocalizationManager.cs new file mode 100644 index 0000000..d0ee61e --- /dev/null +++ b/CFixer/LocalizationManager.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web.Script.Serialization; + +namespace CFixer +{ + internal static class LocalizationManager + { + public static event EventHandler LanguageChanged; + + public const string DefaultLanguage = "en-US"; + public const string ChineseLanguage = "zh-CN"; + + private static readonly Dictionary> LocalizedTexts = + new Dictionary>(StringComparer.OrdinalIgnoreCase); + + private static readonly Dictionary LanguageDisplayNames = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + private static readonly Dictionary FeatureTypeKeys = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["BasicCleanup"] = "issues.disk_cleanup_basic", + ["WingetUpgradeAll"] = "issues.upgrade_all_apps_winget", + + ["BSODDetails"] = "system.show_bsod_details", + ["VerboseStatus"] = "system.enable_logon_verbose_status", + ["SpeedUpShutdown"] = "system.speed_up_shutdown", + ["NetworkThrottling"] = "system.disable_network_throttling", + ["SystemResponsiveness"] = "system.optimize_responsiveness", + ["MenuShowDelay"] = "system.reduce_menu_show_delay", + ["TaskbarEndTask"] = "system.enable_taskbar_end_task", + + ["BrowserSignin"] = "edge.disable_browser_signin_sync", + ["DefaultTopSites"] = "edge.hide_new_tab_sponsored_links", + ["DefautBrowserSetting"] = "edge.disable_default_browser_setting", + ["EdgeCollections"] = "edge.disable_collections_access", + ["EdgeShoppingAssistant"] = "edge.disable_shopping_assistant", + ["FirstRunExperience"] = "edge.hide_first_run_experience", + ["GamerMode"] = "edge.disable_gamer_mode", + ["HubsSidebar"] = "edge.disable_copilot_symbol", + ["ImportOnEachLaunch"] = "edge.disable_cross_browser_import_on_launch", + ["StartupBoost"] = "edge.disable_startup_boost", + ["TabPageQuickLinks"] = "edge.hide_new_tab_quick_links", + ["UserFeedback"] = "edge.disable_user_feedback_submit", + + ["FullContextMenus"] = "ui.show_windows11_full_context_menu", + ["LockScreen"] = "ui.disable_personalized_lock_screen", + ["SearchboxTaskbarMode"] = "ui.hide_taskbar_search_box", + ["ShowOrHideMostUsedApps"] = "ui.hide_start_menu_most_used_apps", + ["ShowTaskViewButton"] = "ui.hide_taskbar_task_view_button", + ["DisableSearchBoxSuggestions"] = "ui.disable_search_box_suggestions", + ["DisableBingSearch"] = "ui.disable_bing_search", + ["StartLayout"] = "ui.enable_start_menu_more_pins", + ["TaskbarAlignment"] = "ui.align_taskbar_start_left", + ["Transparency"] = "ui.disable_transparency_effects", + ["AppDarkMode"] = "ui.enable_dark_mode_apps", + ["SystemDarkMode"] = "ui.enable_dark_mode_system", + ["DisableSnapAssistFlyout"] = "ui.disable_snap_assist_flyout", + + ["GameDVR"] = "gaming.disable_game_dvr", + ["PowerThrottling"] = "gaming.disable_power_throttling", + ["VisualFX"] = "gaming.disable_visual_effects", + + ["ActivityHistory"] = "privacy.disable_activity_history", + ["LocationTracking"] = "privacy.disable_location_tracking", + ["PrivacyExperience"] = "privacy.disable_signin_privacy_experience", + ["Telemetry"] = "privacy.disable_telemetry", + + ["FileExplorerAds"] = "ads.disable_file_explorer", + ["FinishSetupAds"] = "ads.disable_finish_setup", + ["LockScreenAds"] = "ads.disable_lock_screen_tips", + ["PersonalizedAds"] = "ads.disable_personalized", + ["SettingsAds"] = "ads.disable_settings", + ["StartmenuAds"] = "ads.disable_start_menu", + ["TailoredExperiences"] = "ads.disable_tailored_experiences", + ["TipsAndSuggestions"] = "ads.disable_tips_suggestions", + ["WelcomeExperienceAds"] = "ads.disable_welcome_experience", + + ["AskCopilot"] = "ai.remove_ask_copilot_context_menu", + ["ClickToDo"] = "ai.disable_click_to_do", + ["CopilotTaskbar"] = "ai.hide_taskbar_copilot", + ["Recall"] = "ai.disable_recall", + }; + + private static readonly Regex DontWordPattern = new Regex(@"\bdon't\b", RegexOptions.Compiled); + private static readonly Regex DontNoApostrophePattern = new Regex(@"\bdont\b", RegexOptions.Compiled); + private static readonly Regex NonAlphaNumericPattern = new Regex(@"[^a-z0-9]+", RegexOptions.Compiled); + + static LocalizationManager() + { + LoadLanguageFiles(); + } + + public static string CurrentLanguageCode { get; private set; } = DefaultLanguage; + + public static void SetLanguage(string languageCode) + { + string normalizedLanguage = DefaultLanguage; + if (!string.IsNullOrWhiteSpace(languageCode) && LocalizedTexts.ContainsKey(languageCode)) + { + normalizedLanguage = languageCode; + } + + bool changed = !string.Equals(CurrentLanguageCode, normalizedLanguage, StringComparison.OrdinalIgnoreCase); + CurrentLanguageCode = normalizedLanguage; + + var culture = new CultureInfo(CurrentLanguageCode); + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + + if (changed) + { + LanguageChanged?.Invoke(null, EventArgs.Empty); + } + } + + public static string T(string key) + { + if (LocalizedTexts.TryGetValue(CurrentLanguageCode, out var languageValues) && languageValues.TryGetValue(key, out var text)) + { + return text; + } + + if (LocalizedTexts.TryGetValue(DefaultLanguage, out var fallbackLanguage) && fallbackLanguage.TryGetValue(key, out var fallback)) + { + return fallback; + } + + return key; + } + + public static string LocalizeFeatureCategory(string categoryName) + { + return T("feature.category." + categoryName); + } + + public static string LocalizeFeatureName(FeatureNode featureNode) + { + if (featureNode?.Feature != null) + { + var typeName = featureNode.Feature.GetType().Name; + if (FeatureTypeKeys.TryGetValue(typeName, out var semanticKey)) + { + return T("feature.name." + semanticKey); + } + } + + return T("feature.name." + ToFeatureKey(featureNode?.Name ?? string.Empty)); + } + + public static IReadOnlyDictionary GetAvailableLanguages() + { + return new Dictionary(LanguageDisplayNames, StringComparer.OrdinalIgnoreCase); + } + + private static void LoadLanguageFiles() + { + var baseDir = AppDomain.CurrentDomain.BaseDirectory; + var langDir = Path.Combine(baseDir, "lang"); + + LocalizedTexts.Clear(); + LanguageDisplayNames.Clear(); + + if (Directory.Exists(langDir)) + { + foreach (var file in Directory.GetFiles(langDir, "*.json").OrderBy(f => f)) + { + var languageCode = Path.GetFileNameWithoutExtension(file); + LoadLanguageFromFile(file, languageCode); + } + } + + if (!LocalizedTexts.ContainsKey(DefaultLanguage)) + { + LocalizedTexts[DefaultLanguage] = new Dictionary(StringComparer.OrdinalIgnoreCase); + LanguageDisplayNames[DefaultLanguage] = DefaultLanguage; + } + } + + private static void LoadLanguageFromFile(string filePath, string languageCode) + { + if (!File.Exists(filePath)) + { + return; + } + + try + { + var json = File.ReadAllText(filePath); + var serializer = new JavaScriptSerializer(); + var dictionary = serializer.Deserialize>(json); + + if (dictionary != null) + { + LocalizedTexts[languageCode] = new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + + if (dictionary.TryGetValue("__meta.languageName", out var languageName) && !string.IsNullOrWhiteSpace(languageName)) + { + LanguageDisplayNames[languageCode] = languageName; + } + else + { + LanguageDisplayNames[languageCode] = languageCode; + } + } + } + catch + { + // Keep silent and fallback to existing loaded languages. + } + } + + private static string ToFeatureKey(string featureName) + { + if (string.IsNullOrWhiteSpace(featureName)) + { + return string.Empty; + } + + var normalized = featureName.Trim().ToLowerInvariant(); + normalized = DontWordPattern.Replace(normalized, "disable"); + normalized = DontNoApostrophePattern.Replace(normalized, "disable"); + normalized = NonAlphaNumericPattern.Replace(normalized, "_"); + normalized = normalized.Trim('_'); + return normalized; + } + } +} diff --git a/CFixer/MainForm.cs b/CFixer/MainForm.cs index 954c3f1..6a6d681 100644 --- a/CFixer/MainForm.cs +++ b/CFixer/MainForm.cs @@ -11,7 +11,7 @@ namespace CrapFixer { - public partial class MainForm : Form + public partial class MainForm : Form, ILocalizedControl { private NavigationManager _navigationManager; private NavigationHandler _navigationHandler; @@ -23,6 +23,8 @@ public MainForm() { InitializeComponent(); IniStateManager.ApplyWindowState(this); + ApplyLocalization(); + LocalizationManager.LanguageChanged += LocalizationManager_LanguageChanged; // Set up the main navigation manager and logger _navigationManager = new NavigationManager(panelContainer); @@ -71,6 +73,105 @@ private async Task InitializeUI() lblOSInfo.Text = await OSHelper.OSHelper.GetWindowsVersion(); } + private void ApplyLocalization() + { + btnAnalyze.Text = LocalizationManager.T("main.btnAnalyze"); + Windows.Text = LocalizationManager.T("main.tabWindows"); + Apps.Text = LocalizationManager.T("main.tabApplications"); + analyzeMarkedFeatureToolStripMenuItem.Text = LocalizationManager.T("main.ctxAnalyze"); + fixMarkedFeatureToolStripMenuItem.Text = LocalizationManager.T("main.ctxFix"); + restoreMarkedFeatureToolStripMenuItem.Text = LocalizationManager.T("main.ctxRestore"); + helpMarkedFeatureToolStripMenuItem.Text = LocalizationManager.T("main.ctxHelp"); + btnFix.Text = LocalizationManager.T("main.btnFix"); + btnRestore.Text = LocalizationManager.T("main.btnRestore"); + linkUpdateCheck.Text = LocalizationManager.T("main.linkUpdate"); + btnTools.Text = LocalizationManager.T("main.btnTools"); + btnFixer.Text = LocalizationManager.T("main.btnFixer"); + linkSelection.Text = LocalizationManager.T("main.linkSelection"); + + var appsPlaceholder = LocalizationManager.T("main.appsPlaceholder"); + if (checkedListBoxApps.Items.Count == 1 && + checkedListBoxApps.Items[0] is string placeholder && + (placeholder == "No analysis yet" || placeholder == appsPlaceholder)) + { + checkedListBoxApps.Items[0] = appsPlaceholder; + } + + toolTip.SetToolTip(btnGitHub, LocalizationManager.T("main.tooltipSupport")); + toolTip.SetToolTip(lblHeader, LocalizationManager.T("main.tooltipGithub")); + toolTip.SetToolTip(pictureHeader, LocalizationManager.T("main.tooltipGithub")); + } + + public void RefreshLocalization() + { + ApplyLocalization(); + RefreshFeatureTreeLocalization(); + if (panelContainer.Controls.Count > 0 && panelContainer.Controls[0] is ILocalizedControl localizedControl) + { + localizedControl.RefreshLocalization(); + } + } + + private void RefreshFeatureTreeLocalization() + { + var checkedMap = CaptureNodeCheckedState(treeFeatures.Nodes); + FeatureNodeManager.LoadFeatures(treeFeatures); + PluginManager.LoadPlugins(treeFeatures); + RestoreNodeCheckedState(treeFeatures.Nodes, checkedMap); + } + + private static System.Collections.Generic.Dictionary CaptureNodeCheckedState(TreeNodeCollection nodes) + { + var map = new System.Collections.Generic.Dictionary(StringComparer.OrdinalIgnoreCase); + CaptureNodeCheckedStateRecursive(nodes, map); + return map; + } + + private static void CaptureNodeCheckedStateRecursive(TreeNodeCollection nodes, System.Collections.Generic.Dictionary map) + { + foreach (TreeNode node in nodes) + { + string key = string.IsNullOrWhiteSpace(node.Name) ? node.Text.Trim() : node.Name.Trim(); + map[key] = node.Checked; + + if (node.Nodes.Count > 0) + { + CaptureNodeCheckedStateRecursive(node.Nodes, map); + } + } + } + + private static void RestoreNodeCheckedState(TreeNodeCollection nodes, System.Collections.Generic.Dictionary map) + { + foreach (TreeNode node in nodes) + { + string key = string.IsNullOrWhiteSpace(node.Name) ? node.Text.Trim() : node.Name.Trim(); + if (map.TryGetValue(key, out bool checkedState)) + { + node.Checked = checkedState; + } + + if (node.Nodes.Count > 0) + { + RestoreNodeCheckedState(node.Nodes, map); + } + } + } + + private void LocalizationManager_LanguageChanged(object sender, EventArgs e) + { + if (IsDisposed) return; + + if (InvokeRequired) + { + BeginInvoke(new Action(RefreshLocalization)); + } + else + { + RefreshLocalization(); + } + } + // Handles navigation button clicks and switches views accordingly private void NavigationHandler_NavigationButtonClicked(Button button) { @@ -163,10 +264,8 @@ private async void btnFix_Click(object sender, EventArgs e) private void btnRestore_Click(object sender, EventArgs e) { var result = MessageBox.Show( - "⚠️ This will restore all selected features to their original state.\n" + - "Changes made by previous configurations may be reverted.\n\n" + - "Are you sure you want to proceed?", - "Restore Selected Features", + LocalizationManager.T("main.restoreMessage"), + LocalizationManager.T("main.restoreTitle"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); @@ -176,7 +275,7 @@ private void btnRestore_Click(object sender, EventArgs e) foreach (TreeNode node in treeFeatures.Nodes) FeatureNodeManager.RestoreChecked(node); - Logger.Log("↩️ All selected features have been restored.", LogLevel.Info); + Logger.Log(LocalizationManager.T("main.restoreLog"), LogLevel.Info); } } @@ -358,6 +457,8 @@ private void linkUpdateCheck_LinkClicked(object sender, LinkLabelLinkClickedEven private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { + LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged; + if (IniStateManager.IsViewSettingEnabled("SETTINGS", "checkSaveToINI")) { IniStateManager.Save(treeFeatures, this); @@ -371,4 +472,4 @@ private void btnGitHub_Click(object sender, EventArgs e) _navigationManager.SwitchView(new OptionsView()); } } -} \ No newline at end of file +} diff --git a/CFixer/PluginManager.cs b/CFixer/PluginManager.cs index 5aeaf52..cef6c7c 100644 --- a/CFixer/PluginManager.cs +++ b/CFixer/PluginManager.cs @@ -65,7 +65,7 @@ public static void LoadPlugins(TreeView treeView) return; } - var pluginsNode = new TreeNode("Plugins") + var pluginsNode = new TreeNode(LocalizationManager.T("options.plugins")) { BackColor = Color.Magenta, ForeColor = Color.White @@ -394,4 +394,4 @@ public static string GetPluginHelpInfo(TreeNode node) return null; // No info found or invalid node } -} \ No newline at end of file +} diff --git a/CFixer/Program.cs b/CFixer/Program.cs index 3ec39ac..386734b 100644 --- a/CFixer/Program.cs +++ b/CFixer/Program.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using System.Windows.Forms; +using CFixer; namespace CrapFixer { @@ -12,6 +13,9 @@ internal static class Program [STAThread] private static void Main() { + var language = IniStateManager.LoadViewStringSetting("SETTINGS", "Language", LocalizationManager.DefaultLanguage); + LocalizationManager.SetLanguage(language); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); diff --git a/CFixer/Views/AboutView.cs b/CFixer/Views/AboutView.cs index 5ca4a72..bb9bcfd 100644 --- a/CFixer/Views/AboutView.cs +++ b/CFixer/Views/AboutView.cs @@ -1,17 +1,21 @@ using CrapFixer; +using CFixer; using System; using System.Diagnostics; using System.Windows.Forms; namespace Views { - public partial class AboutView : UserControl + public partial class AboutView : UserControl, ILocalizedControl { public AboutView() { InitializeComponent(); InitializeUI(); + ApplyLocalization(); + LocalizationManager.LanguageChanged += LocalizationManager_LanguageChanged; + Disposed += AboutView_Disposed; } private void InitializeUI() @@ -33,6 +37,38 @@ private void InitializeUI() comboBoxCurrency.SelectedIndex = 0; } + private void ApplyLocalization() + { + label1.Text = LocalizationManager.T("about.description"); + btnDonate.Text = LocalizationManager.T("about.donate"); + lblCopyright.Text = LocalizationManager.T("about.copyright"); + } + + public void RefreshLocalization() + { + ApplyLocalization(); + } + + private void LocalizationManager_LanguageChanged(object sender, EventArgs e) + { + if (IsDisposed) return; + + if (InvokeRequired) + { + BeginInvoke(new Action(RefreshLocalization)); + } + else + { + RefreshLocalization(); + } + } + + private void AboutView_Disposed(object sender, EventArgs e) + { + LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged; + Disposed -= AboutView_Disposed; + } + private void linkGitHub_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { Process.Start("https://github.com/builtbybel/CrapFixer/releases"); @@ -45,12 +81,12 @@ private void btnDonate_Click(object sender, EventArgs e) if (string.IsNullOrEmpty(amount) || string.IsNullOrEmpty(currency)) { - MessageBox.Show("Please select an amount and a currency."); + MessageBox.Show(LocalizationManager.T("about.donationValidation")); return; } string email = "belim@builtbybel.com"; - string purpose = Uri.EscapeDataString("Support Development of the CrapFixer app."); + string purpose = Uri.EscapeDataString(LocalizationManager.T("about.donationPurpose")); string returnUrl = Uri.EscapeDataString("https://github.com/Belim/support"); string cancelUrl = Uri.EscapeDataString("https://github.com/builtbybel/CrapFixer"); diff --git a/CFixer/Views/OptionsView.cs b/CFixer/Views/OptionsView.cs index ce764b2..7847d5e 100644 --- a/CFixer/Views/OptionsView.cs +++ b/CFixer/Views/OptionsView.cs @@ -4,17 +4,57 @@ namespace CFixer.Views { - public partial class OptionsView : UserControl + public partial class OptionsView : UserControl, ILocalizedControl { private NavigationManager subNavigation; public OptionsView() { InitializeComponent(); + ApplyLocalization(); + LocalizationManager.LanguageChanged += LocalizationManager_LanguageChanged; + Disposed += OptionsView_Disposed; subNavigation = new NavigationManager(panelSubContent); subNavigation.SwitchView(new AboutView()); // Startsite } + private void ApplyLocalization() + { + btnSettingsMenu.Text = LocalizationManager.T("options.settings"); + btnPluginsMenu.Text = LocalizationManager.T("options.plugins"); + btnViveMenu.Text = LocalizationManager.T("options.features"); + btnAboutMenu.Text = LocalizationManager.T("options.about"); + } + + public void RefreshLocalization() + { + ApplyLocalization(); + if (panelSubContent.Controls.Count > 0 && panelSubContent.Controls[0] is ILocalizedControl localizedControl) + { + localizedControl.RefreshLocalization(); + } + } + + private void LocalizationManager_LanguageChanged(object sender, EventArgs e) + { + if (IsDisposed) return; + + if (InvokeRequired) + { + BeginInvoke(new Action(RefreshLocalization)); + } + else + { + RefreshLocalization(); + } + } + + private void OptionsView_Disposed(object sender, EventArgs e) + { + LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged; + Disposed -= OptionsView_Disposed; + } + private void btnAboutMenu_Click(object sender, EventArgs e) { subNavigation.SwitchView(new AboutView()); diff --git a/CFixer/Views/PluginsView.cs b/CFixer/Views/PluginsView.cs index 211cd21..8aba16b 100644 --- a/CFixer/Views/PluginsView.cs +++ b/CFixer/Views/PluginsView.cs @@ -9,11 +9,12 @@ namespace CFixer.Views { - public partial class PluginsView : UserControl + public partial class PluginsView : UserControl, ILocalizedControl { private List plugins = new List(); // All plugins from the manifest, so full squad private List visiblePlugins = new List(); // Plugins currently shown in the UI (filtered or not) private HashSet installedPlugins = new HashSet(); // Names of plugins already installed on disk + private string _searchPlaceholder = string.Empty; private const string manifestUrl = "https://raw.githubusercontent.com/builtbybel/CrapFixer/main/plugins/plugins_manifest.txt"; @@ -27,6 +28,60 @@ public class PluginEntry public PluginsView() { InitializeComponent(); + ApplyLocalization(); + LocalizationManager.LanguageChanged += LocalizationManager_LanguageChanged; + Disposed += PluginsView_Disposed; + } + + private void ApplyLocalization() + { + btnDescription.Text = LocalizationManager.T("plugins.description"); + btnPluginInstall.Text = LocalizationManager.T("plugins.install"); + btnPluginUpdateAll.Text = LocalizationManager.T("plugins.updateAll"); + btnPluginRemove.Text = LocalizationManager.T("plugins.remove"); + btnPluginEdit.Text = LocalizationManager.T("plugins.edit"); + btnHelp.Text = LocalizationManager.T("plugins.help"); + btnPluginSubmit.Text = LocalizationManager.T("plugins.submit"); + var placeholder = LocalizationManager.T("plugins.search"); + if (string.IsNullOrWhiteSpace(textSearch.Text) || textSearch.Text.Equals(_searchPlaceholder, StringComparison.OrdinalIgnoreCase)) + { + textSearch.Text = placeholder; + } + _searchPlaceholder = placeholder; + linkPluginUsage.Text = LocalizationManager.T("plugins.usage"); + columnHeader1.Text = LocalizationManager.T("plugins.colPlugin"); + columnHeader2.Text = LocalizationManager.T("plugins.colInstalled"); + columnHeader3.Text = LocalizationManager.T("plugins.colType"); + } + + public void RefreshLocalization() + { + ApplyLocalization(); + var query = textSearch.Text.Trim().ToLower(); + if (query == _searchPlaceholder.ToLower()) + query = string.Empty; + + UpdateVisiblePlugins(query); + } + + private void LocalizationManager_LanguageChanged(object sender, EventArgs e) + { + if (IsDisposed) return; + + if (InvokeRequired) + { + BeginInvoke(new Action(RefreshLocalization)); + } + else + { + RefreshLocalization(); + } + } + + private void PluginsView_Disposed(object sender, EventArgs e) + { + LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged; + Disposed -= PluginsView_Disposed; } private async void PluginsView_Load(object sender, EventArgs e) @@ -75,7 +130,7 @@ private async Task LoadPlugins() } catch (Exception ex) { - MessageBox.Show("Error loading plugins: " + ex.Message); + MessageBox.Show(LocalizationManager.T("plugins.msgLoadError") + ex.Message); } } @@ -153,7 +208,7 @@ private async Task InstallPlugins(bool force = false) var checkedItems = listPlugins.CheckedItems.Cast().ToList(); if (checkedItems.Count == 0) { - MessageBox.Show("Please check one or more plugins to download."); + MessageBox.Show(LocalizationManager.T("plugins.msgSelectForDownload")); return; } @@ -187,12 +242,12 @@ private async Task InstallPlugins(bool force = false) { await client.DownloadFileTaskAsync(new Uri(plugin.Url), file); installedPlugins.Add(Path.GetFileName(plugin.Url)); - item.SubItems[1].Text = "Yes"; // Update Installed column + item.SubItems[1].Text = LocalizationManager.T("plugins.stateYes"); // Update Installed column //item.Checked = true; // Ensure checked } catch (Exception ex) { - MessageBox.Show($"Failed to download {plugin.Name}: {ex.Message}"); + MessageBox.Show(string.Format(LocalizationManager.T("plugins.msgDownloadFailed"), plugin.Name, ex.Message)); } progressBarDownload.Value = ++done; @@ -224,10 +279,10 @@ private async void btnPluginUpdateAll_Click(object sender, EventArgs e) // Update the status of all plugins to "Updated" foreach (ListViewItem item in listPlugins.Items) { - item.SubItems[1].Text = "Updated"; + item.SubItems[1].Text = LocalizationManager.T("plugins.stateUpdated"); } - MessageBox.Show("All plugins updated."); + MessageBox.Show(LocalizationManager.T("plugins.msgAllUpdated")); } private void btnPluginRemove_Click(object sender, EventArgs e) @@ -238,7 +293,7 @@ private void btnPluginRemove_Click(object sender, EventArgs e) if (checkedItems.Count == 0) { - MessageBox.Show("No plugins selected."); + MessageBox.Show(LocalizationManager.T("plugins.msgNoneSelected")); return; } @@ -254,18 +309,18 @@ private void btnPluginRemove_Click(object sender, EventArgs e) File.Delete(path); installedPlugins.Remove(Path.GetFileName(plugin.Url)); - item.SubItems[1].Text = "No"; + item.SubItems[1].Text = LocalizationManager.T("plugins.stateNo"); item.Checked = false; } - MessageBox.Show("Selected plugins removed."); + MessageBox.Show(LocalizationManager.T("plugins.msgRemoved")); } private void btnPluginEdit_Click(object sender, EventArgs e) { if (listPlugins.SelectedItems.Count == 0) { - MessageBox.Show("Please select a plugin first."); + MessageBox.Show(LocalizationManager.T("plugins.msgSelectFirst")); return; } @@ -276,7 +331,7 @@ private void btnPluginEdit_Click(object sender, EventArgs e) if (!File.Exists(path)) { - MessageBox.Show("Plugin file not found. Please install the plugin first."); + MessageBox.Show(LocalizationManager.T("plugins.msgPluginNotFound")); return; } @@ -288,7 +343,7 @@ private void btnPluginEdit_Click(object sender, EventArgs e) } catch (Exception ex) { - MessageBox.Show("Could not open plugin: " + ex.Message); + MessageBox.Show(LocalizationManager.T("plugins.msgOpenPluginFailed") + ex.Message); } } @@ -299,14 +354,14 @@ private void btnHelp_Click(object sender, EventArgs e) { if (listPlugins.SelectedItems.Count == 0) { - MessageBox.Show("Please select a plugin first."); + MessageBox.Show(LocalizationManager.T("plugins.msgSelectFirst")); return; } var plugin = listPlugins.SelectedItems[0].Tag as PluginEntry; if (plugin != null) { - MessageBox.Show(plugin.Description, $"Info: {plugin.Name}"); + MessageBox.Show(plugin.Description, string.Format(LocalizationManager.T("plugins.msgInfoTitle"), plugin.Name)); } } @@ -351,15 +406,15 @@ private void UpdateVisiblePlugins(string query = "") } else if (Path.GetExtension(plugin.Url).Equals(".ps1", StringComparison.OrdinalIgnoreCase)) { - type = "Powershell"; + type = LocalizationManager.T("plugins.typePowershell"); } else { - type = "Other"; + type = LocalizationManager.T("plugins.typeOther"); } - var item = new ListViewItem(plugin.Name); - item.SubItems.Add(isInstalled ? "Yes" : "No"); + var item = new ListViewItem(GetLocalizedPluginName(plugin.Name)); + item.SubItems.Add(isInstalled ? LocalizationManager.T("plugins.stateYes") : LocalizationManager.T("plugins.stateNo")); item.SubItems.Add(type); item.Tag = plugin; item.Checked = isInstalled; @@ -374,6 +429,13 @@ private void UpdateVisiblePlugins(string query = "") } } + private static string GetLocalizedPluginName(string pluginName) + { + var key = "plugins.name." + pluginName; + var localized = LocalizationManager.T(key); + return string.Equals(localized, key, StringComparison.Ordinal) ? pluginName : localized; + } + private void textSearch_TextChanged(object sender, EventArgs e) { @@ -400,4 +462,4 @@ private void linkPluginUsage_LinkClicked(object sender, LinkLabelLinkClickedEven Process.Start("https://github.com/builtbybel/CrapFixer/blob/main/plugins/DemoPluginPack.ps1"); } } -} \ No newline at end of file +} diff --git a/CFixer/Views/SettingsView.Designer.cs b/CFixer/Views/SettingsView.Designer.cs index 652081a..a691179 100644 --- a/CFixer/Views/SettingsView.Designer.cs +++ b/CFixer/Views/SettingsView.Designer.cs @@ -32,6 +32,8 @@ private void InitializeComponent() this.checkBox2 = new System.Windows.Forms.CheckBox(); this.button1 = new System.Windows.Forms.Button(); this.checkInstallIcons = new System.Windows.Forms.CheckBox(); + this.labelLanguage = new System.Windows.Forms.Label(); + this.comboLanguage = new System.Windows.Forms.ComboBox(); this.SuspendLayout(); // // checkSaveToINI @@ -83,11 +85,32 @@ private void InitializeComponent() this.checkInstallIcons.UseVisualStyleBackColor = true; this.checkInstallIcons.CheckedChanged += new System.EventHandler(this.checkInstallIcons_CheckedChanged); // + // labelLanguage + // + this.labelLanguage.AutoSize = true; + this.labelLanguage.Location = new System.Drawing.Point(15, 141); + this.labelLanguage.Name = "labelLanguage"; + this.labelLanguage.Size = new System.Drawing.Size(55, 13); + this.labelLanguage.TabIndex = 5; + this.labelLanguage.Text = "Language"; + // + // comboLanguage + // + this.comboLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboLanguage.FormattingEnabled = true; + this.comboLanguage.Location = new System.Drawing.Point(76, 138); + this.comboLanguage.Name = "comboLanguage"; + this.comboLanguage.Size = new System.Drawing.Size(170, 21); + this.comboLanguage.TabIndex = 6; + this.comboLanguage.SelectedIndexChanged += new System.EventHandler(this.comboLanguage_SelectedIndexChanged); + // // SettingsView // this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(251)))), ((int)(((byte)(251)))), ((int)(((byte)(251))))); + this.Controls.Add(this.comboLanguage); + this.Controls.Add(this.labelLanguage); this.Controls.Add(this.checkInstallIcons); this.Controls.Add(this.button1); this.Controls.Add(this.checkBox2); @@ -106,5 +129,7 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox checkBox2; private System.Windows.Forms.Button button1; private System.Windows.Forms.CheckBox checkInstallIcons; + private System.Windows.Forms.Label labelLanguage; + private System.Windows.Forms.ComboBox comboLanguage; } } diff --git a/CFixer/Views/SettingsView.cs b/CFixer/Views/SettingsView.cs index 7c12551..ab47deb 100644 --- a/CFixer/Views/SettingsView.cs +++ b/CFixer/Views/SettingsView.cs @@ -7,13 +7,19 @@ namespace CFixer.Views { - public partial class SettingsView : UserControl + public partial class SettingsView : UserControl, ILocalizedControl { + private bool _isInitializingLanguage; + public SettingsView() { InitializeComponent(); + PopulateLanguageOptions(LocalizationManager.CurrentLanguageCode); LoadSettings(); + ApplyLocalization(); CheckIfIconsInstalled(); + LocalizationManager.LanguageChanged += LocalizationManager_LanguageChanged; + Disposed += SettingsView_Disposed; } /// @@ -27,6 +33,7 @@ public void SaveSettings() }; IniStateManager.SaveViewSettings("SETTINGS", settings); + IniStateManager.SaveViewStringSetting("SETTINGS", "Language", GetSelectedLanguageCode()); } /// @@ -36,6 +43,89 @@ public void LoadSettings() { var settings = IniStateManager.LoadViewSettings("SETTINGS"); checkSaveToINI.Checked = settings.GetValueOrDefault(nameof(checkSaveToINI), false); + + var savedLanguage = IniStateManager.LoadViewStringSetting("SETTINGS", "Language", LocalizationManager.CurrentLanguageCode); + SetLanguageSelection(savedLanguage); + } + + private void ApplyLocalization() + { + button1.Text = LocalizationManager.T("settings.sectionBasic"); + checkSaveToINI.Text = LocalizationManager.T("settings.saveIni"); + checkBox2.Text = LocalizationManager.T("settings.superPlugins"); + checkInstallIcons.Text = LocalizationManager.T("settings.installIcons"); + labelLanguage.Text = LocalizationManager.T("settings.languageLabel"); + + PopulateLanguageOptions(GetSelectedLanguageCode()); + } + + public void RefreshLocalization() + { + ApplyLocalization(); + } + + private void PopulateLanguageOptions(string selectedLanguageCode) + { + _isInitializingLanguage = true; + try + { + comboLanguage.Items.Clear(); + + var languages = LocalizationManager.GetAvailableLanguages(); + var options = languages + .OrderBy(l => l.Key.Equals(LocalizationManager.DefaultLanguage, StringComparison.OrdinalIgnoreCase) ? 0 : 1) + .ThenBy(l => l.Key, StringComparer.OrdinalIgnoreCase) + .ToList(); + + foreach (var option in options) + { + comboLanguage.Items.Add(option); + } + + comboLanguage.DisplayMember = "Value"; + comboLanguage.ValueMember = "Key"; + + var targetCode = languages.ContainsKey(selectedLanguageCode) + ? selectedLanguageCode + : LocalizationManager.DefaultLanguage; + + int index = options.FindIndex(o => o.Key == targetCode); + comboLanguage.SelectedIndex = index >= 0 ? index : (options.Count > 0 ? 0 : -1); + } + finally + { + _isInitializingLanguage = false; + } + } + + private string GetSelectedLanguageCode() + { + if (comboLanguage.SelectedItem is KeyValuePair selected) + { + return selected.Key; + } + + return LocalizationManager.DefaultLanguage; + } + + private void SetLanguageSelection(string languageCode) + { + var languages = LocalizationManager.GetAvailableLanguages(); + var normalizedCode = languages.ContainsKey(languageCode) + ? languageCode + : LocalizationManager.DefaultLanguage; + + for (int i = 0; i < comboLanguage.Items.Count; i++) + { + var option = (KeyValuePair)comboLanguage.Items[i]; + if (option.Key == normalizedCode) + { + comboLanguage.SelectedIndex = i; + return; + } + } + + comboLanguage.SelectedIndex = 0; } private void SettingsView_Leave(object sender, EventArgs e) @@ -56,12 +146,11 @@ private void CheckIfIconsInstalled() private async void checkInstallIcons_CheckedChanged(object sender, EventArgs e) { var result = MessageBox.Show( - "By default, buttons have no icons to reduce app size. Enable this to download and display navigation icons." + - "\nWould you like to install it now?", - "Icons Pack Detected", - MessageBoxButtons.YesNo, - MessageBoxIcon.Information - ); + LocalizationManager.T("settings.iconPrompt"), + LocalizationManager.T("settings.iconPromptTitle"), + MessageBoxButtons.YesNo, + MessageBoxIcon.Information + ); if (result == DialogResult.Yes) { @@ -73,9 +162,9 @@ private async void checkInstallIcons_CheckedChanged(object sender, EventArgs e) string[] iconFiles = new string[] { - "fixer.png", - "options.png", - "restore.png" + "fixer.png", + "options.png", + "restore.png" }; string baseUrl = "https://raw.githubusercontent.com/builtbybel/CrapFixer/main/icons/"; @@ -91,8 +180,8 @@ private async void checkInstallIcons_CheckedChanged(object sender, EventArgs e) } MessageBox.Show( - "All icons have been successfully installed in the 'icons' folder!\n\n💖 Love CrapFixer? Consider supporting me with a small donation to keep this tool alive and improving!", - "Icons Installed", + LocalizationManager.T("settings.iconInstalled"), + LocalizationManager.T("settings.iconInstalledTitle"), MessageBoxButtons.OK, MessageBoxIcon.Information ); @@ -102,12 +191,52 @@ private async void checkInstallIcons_CheckedChanged(object sender, EventArgs e) } catch (Exception ex) { - MessageBox.Show("❌ An error occurred while downloading the icons:\n" + ex.Message, - "Download Failed", + MessageBox.Show( + LocalizationManager.T("settings.iconDownloadFailed") + ex.Message, + LocalizationManager.T("settings.iconDownloadFailedTitle"), MessageBoxButtons.OK, - MessageBoxIcon.Error); + MessageBoxIcon.Error + ); } } } + + private void comboLanguage_SelectedIndexChanged(object sender, EventArgs e) + { + if (_isInitializingLanguage) + { + return; + } + + var selectedLanguage = GetSelectedLanguageCode(); + if (selectedLanguage == LocalizationManager.CurrentLanguageCode) + { + return; + } + + LocalizationManager.SetLanguage(selectedLanguage); + SaveSettings(); + ApplyLocalization(); + } + + private void LocalizationManager_LanguageChanged(object sender, EventArgs e) + { + if (IsDisposed) return; + + if (InvokeRequired) + { + BeginInvoke(new Action(RefreshLocalization)); + } + else + { + RefreshLocalization(); + } + } + + private void SettingsView_Disposed(object sender, EventArgs e) + { + LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged; + Disposed -= SettingsView_Disposed; + } } -} \ No newline at end of file +} diff --git a/CFixer/Views/ViveView.cs b/CFixer/Views/ViveView.cs index 627e82d..9b6e8b3 100644 --- a/CFixer/Views/ViveView.cs +++ b/CFixer/Views/ViveView.cs @@ -10,7 +10,7 @@ namespace CFixer.Views { - public partial class ViveView : UserControl + public partial class ViveView : UserControl, ILocalizedControl { private List featureList; private string viveToolPath; @@ -18,6 +18,9 @@ public partial class ViveView : UserControl public ViveView() { InitializeComponent(); + ApplyLocalization(); + LocalizationManager.LanguageChanged += LocalizationManager_LanguageChanged; + Disposed += ViveView_Disposed; IsViveToolAvailable(); InitFeatureList(); LoadFeaturesToGrid(); @@ -26,17 +29,60 @@ public ViveView() _ = UpdateFeatureStatusFromSystem(); } + private void ApplyLocalization() + { + btnApply.Text = LocalizationManager.T("vive.applySelected"); + btnDescription.Text = LocalizationManager.T("vive.description"); + linkPluginUsage.Text = LocalizationManager.T("vive.moreInfos"); + lblCustomIds.Text = LocalizationManager.T("vive.customIds"); + btnApplyCustom.Text = LocalizationManager.T("vive.applyCustom"); + + NameColumn.HeaderText = LocalizationManager.T("vive.colFeature"); + IdColumn.HeaderText = LocalizationManager.T("vive.colId"); + StatusColumn.HeaderText = LocalizationManager.T("vive.colStatus"); + InfoColumn.HeaderText = LocalizationManager.T("vive.colInfo"); + } + + public void RefreshLocalization() + { + ApplyLocalization(); + IsViveToolAvailable(); + UpdateGridFeatureNamesFromLocalization(); + _ = UpdateFeatureStatusFromSystem(); + } + + private void LocalizationManager_LanguageChanged(object sender, EventArgs e) + { + if (IsDisposed) return; + + if (InvokeRequired) + { + BeginInvoke(new Action(RefreshLocalization)); + } + else + { + RefreshLocalization(); + } + } + + private void ViveView_Disposed(object sender, EventArgs e) + { + LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged; + Disposed -= ViveView_Disposed; + } + private void IsViveToolAvailable() { string pluginsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins"); - var viveFolder = Directory.GetDirectories(pluginsDir) - .FirstOrDefault(dir => Path.GetFileName(dir).ToLower().StartsWith("vive")); + var viveFolder = Directory.Exists(pluginsDir) + ? Directory.GetDirectories(pluginsDir).FirstOrDefault(dir => Path.GetFileName(dir).ToLower().StartsWith("vive")) + : null; if (viveFolder == null) { viveToolPath = null; - btnDescription.Text = "Enable experimental and hidden features (Disabled)"; + btnDescription.Text = LocalizationManager.T("vive.descriptionDisabled"); return; } @@ -44,7 +90,12 @@ private void IsViveToolAvailable() if (File.Exists(exePath)) { viveToolPath = exePath; - btnDescription.Text = "Enable experimental and hidden features (Enabled)"; + btnDescription.Text = LocalizationManager.T("vive.descriptionEnabled"); + } + else + { + viveToolPath = null; + btnDescription.Text = LocalizationManager.T("vive.descriptionDisabled"); } } @@ -59,6 +110,7 @@ private void InitFeatureList() { Ids = new List {47205210, 49221331, 49381526, 49402389, 49820095, 55495322, 48433719}, Name = "Enable the redesigned Windows 11 Start menu", + NameKey = "vive.feature.windows11_redesigned_start_menu", InfoUrl = "https://www.neowin.net/guides/how-to-enable-the-redesigned-windows-11-start-menu/", Enabled = false }, @@ -66,6 +118,7 @@ private void InitFeatureList() { Ids = new List {52467192,53079680}, Name = "Enable Text extractor in Snipping Tool", + NameKey = "vive.feature.snipping_tool_text_extractor", InfoUrl = "https://blogs.windows.com/windows-insider/2025/04/15/text-extractor-in-snipping-tool-begins-rolling-out-to-windows-insiders/", Enabled = false } @@ -73,6 +126,7 @@ private void InitFeatureList() { Ids = new List {45624564}, Name = "Enable Drag Tray Share UI", + NameKey = "vive.feature.drag_tray_share_ui", InfoUrl = "https://www.neowin.net/news/windows-11-is-getting-a-quirky-new-way-to-share-files/", Enabled = false } @@ -92,10 +146,35 @@ private void LoadFeaturesToGrid() var row = dataGridView.Rows[rowIndex]; row.Cells["EnabledColumn"].Value = feature.Enabled; - row.Cells["NameColumn"].Value = feature.Name; + row.Cells["NameColumn"].Value = LocalizeFeatureName(feature); row.Cells["IdColumn"].Value = feature.IdsAsString; row.Cells["InfoColumn"].Value = feature.InfoUrl; - row.Cells["StatusColumn"].Value = "Unknown"; + row.Cells["StatusColumn"].Value = LocalizationManager.T("vive.statusUnknown"); + } + } + + private string LocalizeFeatureName(ViveFeature feature) + { + if (feature == null || string.IsNullOrWhiteSpace(feature.NameKey)) + { + return feature?.Name ?? string.Empty; + } + + string localized = LocalizationManager.T(feature.NameKey); + return string.Equals(localized, feature.NameKey, StringComparison.OrdinalIgnoreCase) + ? feature.Name + : localized; + } + + private void UpdateGridFeatureNamesFromLocalization() + { + int count = Math.Min(dataGridView.Rows.Count, featureList?.Count ?? 0); + for (int i = 0; i < count; i++) + { + var row = dataGridView.Rows[i]; + if (row.IsNewRow) continue; + + row.Cells["NameColumn"].Value = LocalizeFeatureName(featureList[i]); } } @@ -106,7 +185,11 @@ private void ApplyFeature(List ids, bool enable) { if (string.IsNullOrEmpty(viveToolPath)) { - MessageBox.Show("ViVeTool not found. Please ensure it is installed in the plugins folder.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show( + LocalizationManager.T("vive.msgToolMissing"), + LocalizationManager.T("vive.msgErrorTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Error); return; } @@ -144,7 +227,11 @@ private async void btnViveApply_Click(object sender, EventArgs e) Task.Delay(1000).Wait(); await UpdateFeatureStatusFromSystem(); // Refresh the status after applying changes - MessageBox.Show("Features have been applied.", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show( + LocalizationManager.T("vive.msgFeaturesApplied"), + LocalizationManager.T("vive.msgDoneTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Information); } /// @@ -157,7 +244,11 @@ private Dictionary QueryCurrentFeatureStates() if (string.IsNullOrEmpty(viveToolPath)) { - MessageBox.Show("ViVeTool not found. Please ensure it is installed in the plugins folder.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show( + LocalizationManager.T("vive.msgToolMissing"), + LocalizationManager.T("vive.msgErrorTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Error); return statusMap; } @@ -223,11 +314,11 @@ private void UpdateGridWithStatus(Dictionary systemStatus) int enabledCount = ids.Count(id => systemStatus.ContainsKey(id) && systemStatus[id]); - string status = "Disabled"; + string status = LocalizationManager.T("vive.statusDisabled"); if (enabledCount == ids.Count) - status = "All Enabled"; + status = LocalizationManager.T("vive.statusAllEnabled"); else if (enabledCount > 0) - status = "Partially Enabled"; + status = LocalizationManager.T("vive.statusPartiallyEnabled"); row.Cells["StatusColumn"].Value = status; } @@ -250,7 +341,11 @@ private void dataGridView_CellContentClick(object sender, DataGridViewCellEventA } catch (Exception ex) { - MessageBox.Show($"Failed to open link:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show( + string.Format(LocalizationManager.T("vive.msgOpenLinkFailed"), ex.Message), + LocalizationManager.T("vive.msgErrorTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Error); } } } @@ -263,6 +358,7 @@ public class ViveFeature { public List Ids { get; set; } public string Name { get; set; } + public string NameKey { get; set; } public bool Enabled { get; set; } public string InfoUrl { get; set; } @@ -271,10 +367,7 @@ public class ViveFeature private void linkPluginUsage_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - MessageBox.Show("This plugin uses ViVeTool to enable hidden Windows features.\n" + - "Please download ViVeTool (e.g. 'ViVeTool-v0.3.x-IntelAmd') from:\n" + - "https://github.com/thebookisclosed/ViVe/releases\n" + - "Extract it and place the contents into a subfolder inside the 'plugins' directory.\n\n"); + MessageBox.Show(LocalizationManager.T("vive.msgUsage")); } private void btnApplyCustom_Click(object sender, EventArgs e) @@ -283,7 +376,11 @@ private void btnApplyCustom_Click(object sender, EventArgs e) if (string.IsNullOrWhiteSpace(input)) { - MessageBox.Show("Please enter one or more feature IDs.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning); + MessageBox.Show( + LocalizationManager.T("vive.msgEnterIds"), + LocalizationManager.T("vive.msgInvalidInputTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Warning); return; } @@ -299,24 +396,31 @@ private void btnApplyCustom_Click(object sender, EventArgs e) } else { - MessageBox.Show($"Invalid ID: '{part}'", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show( + string.Format(LocalizationManager.T("vive.msgInvalidId"), part), + LocalizationManager.T("vive.msgErrorTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Error); return; } } if (idList.Count == 0) { - MessageBox.Show("No valid IDs found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show( + LocalizationManager.T("vive.msgNoValidIds"), + LocalizationManager.T("vive.msgErrorTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Error); return; } // Ask whether to enable or disable var result = MessageBox.Show( - $"Do you want to ENABLE these features?\n\n{string.Join(", ", idList)}\n\n" + - "Yes = Enable\nNo = Disable\nCancel = Abort", - "Confirm Action", - MessageBoxButtons.YesNoCancel, - MessageBoxIcon.Question); + string.Format(LocalizationManager.T("vive.msgConfirm"), string.Join(", ", idList)), + LocalizationManager.T("vive.msgConfirmTitle"), + MessageBoxButtons.YesNoCancel, + MessageBoxIcon.Question); if (result == DialogResult.Cancel) return; @@ -324,7 +428,11 @@ private void btnApplyCustom_Click(object sender, EventArgs e) ApplyFeature(idList, enable); - MessageBox.Show("Custom feature action sent to ViVeTool.", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show( + LocalizationManager.T("vive.msgCustomSent"), + LocalizationManager.T("vive.msgDoneTitle"), + MessageBoxButtons.OK, + MessageBoxIcon.Information); } } -} \ No newline at end of file +} diff --git a/CFixer/lang/en-US.json b/CFixer/lang/en-US.json new file mode 100644 index 0000000..2330872 --- /dev/null +++ b/CFixer/lang/en-US.json @@ -0,0 +1,168 @@ +{ + "__meta.languageName": "English (United States)", + "about.copyright": "Copyright (c) 2025 A Belim app creation", + "about.description": "You can download the latest version, report bugs and submit feature requests at the following GitHub page.", + "about.donate": "Donate", + "about.donationPurpose": "Support Development of the CrapFixer app.", + "about.donationValidation": "Please select an amount and a currency.", + "common.chinese": "Chinese (Simplified)", + "common.english": "English", + "common.language": "Language", + "feature.category.Ads": "Ads", + "feature.category.AI": "AI", + "feature.category.Gaming": "Gaming", + "feature.category.Issues": "Issues", + "feature.category.MS Edge": "MS Edge", + "feature.category.Privacy": "Privacy", + "feature.category.System": "System", + "feature.category.UI": "UI", + "feature.name.ads.disable_file_explorer": "Disable File Explorer Ads", + "feature.name.ads.disable_finish_setup": "Disable Finish Setup Ads", + "feature.name.ads.disable_lock_screen_tips": "Disable Lock Screen Tips and Ads", + "feature.name.ads.disable_personalized": "Disable Personalized Ads", + "feature.name.ads.disable_settings": "Disable Settings Ads", + "feature.name.ads.disable_start_menu": "Disable Start menu Ads", + "feature.name.ads.disable_tailored_experiences": "Disable Tailored experiences", + "feature.name.ads.disable_tips_suggestions": "Disable General Tips and Ads", + "feature.name.ads.disable_welcome_experience": "Disable Welcome Experience Ads", + "feature.name.ai.disable_click_to_do": "Disable Click to Do (Only Copilot+ PCs)", + "feature.name.ai.disable_recall": "Turn off Recall in Windows 11", + "feature.name.ai.hide_taskbar_copilot": "Don't Show Copilot in Taskbar", + "feature.name.ai.remove_ask_copilot_context_menu": "Remove Ask Copilot from context menu", + "feature.name.edge.disable_browser_signin_sync": "Disable Browser sign in and sync services", + "feature.name.edge.disable_collections_access": "Disable Access to Collections feature", + "feature.name.edge.disable_copilot_symbol": "Disable Copilot Symbol in Edge", + "feature.name.edge.disable_cross_browser_import_on_launch": "Don't Allow to Import of data from other browsers on each launch", + "feature.name.edge.disable_default_browser_setting": "Disable Microsoft Edge as default browser", + "feature.name.edge.disable_gamer_mode": "Disable Gamer Mode", + "feature.name.edge.disable_shopping_assistant": "Disable Shopping assistant", + "feature.name.edge.disable_startup_boost": "Disable Start Boost", + "feature.name.edge.disable_user_feedback_submit": "Don't Submit user feedback option", + "feature.name.edge.hide_first_run_experience": "Don't Show First Run Experience", + "feature.name.edge.hide_new_tab_quick_links": "Don't Show Quick links in new tab page", + "feature.name.edge.hide_new_tab_sponsored_links": "Don't Show Sponsored links in new tab page", + "feature.name.gaming.disable_game_dvr": "Disable Game DVR", + "feature.name.gaming.disable_power_throttling": "Disable Power Throttling", + "feature.name.gaming.disable_visual_effects": "Disable Visual Effects", + "feature.name.issues.disk_cleanup_basic": "Basic Disk Cleanup", + "feature.name.issues.upgrade_all_apps_winget": "Winget App Updates", + "feature.name.privacy.disable_activity_history": "Disable activity history", + "feature.name.privacy.disable_location_tracking": "Disable location tracking", + "feature.name.privacy.disable_signin_privacy_experience": "Disable Privacy Settings Experience at sign-in", + "feature.name.privacy.disable_telemetry": "Turn off Telemetry data collection", + "feature.name.system.disable_network_throttling": "Disable Network Throttling", + "feature.name.system.enable_logon_verbose_status": "Enable Verbose Logon status messages", + "feature.name.system.enable_taskbar_end_task": "Enable End Task", + "feature.name.system.optimize_responsiveness": "Optimize System Responsiveness", + "feature.name.system.reduce_menu_show_delay": "Speed Up Menu Show Delay", + "feature.name.system.show_bsod_details": "Show BSOD details instead of sad smiley", + "feature.name.system.speed_up_shutdown": "Speed Up Shutdown Time", + "feature.name.ui.align_taskbar_start_left": "Align Start button to left", + "feature.name.ui.disable_bing_search": "Disable Bing Search", + "feature.name.ui.disable_personalized_lock_screen": "Don't use personalized lock screen", + "feature.name.ui.disable_search_box_suggestions": "Disable Search Box Suggestions", + "feature.name.ui.disable_snap_assist_flyout": "Disable Snap Assist Flyout", + "feature.name.ui.disable_transparency_effects": "Disable Transparency Effects", + "feature.name.ui.enable_dark_mode_apps": "Enable Dark Mode for Apps", + "feature.name.ui.enable_dark_mode_system": "Enable Dark Mode for System", + "feature.name.ui.enable_start_menu_more_pins": "Pin more Apps on start menu", + "feature.name.ui.hide_start_menu_most_used_apps": "Hide Most used apps in start menu", + "feature.name.ui.hide_taskbar_search_box": "Hide search box on taskbar", + "feature.name.ui.hide_taskbar_task_view_button": "Hide Task view button on taskbar", + "feature.name.ui.show_windows11_full_context_menu": "Show Full context menus in Windows 11", + "main.appsPlaceholder": "No analysis yet", + "main.btnAnalyze": "&Analyze", + "main.btnFix": "Run &Fixer", + "main.btnFixer": "&Fixer", + "main.btnRestore": "&Restore", + "main.btnTools": "&Tools", + "main.ctxAnalyze": "Analyze", + "main.ctxFix": "Fix", + "main.ctxHelp": "Help", + "main.ctxRestore": "Restore", + "main.linkSelection": "Select all", + "main.linkUpdate": "Check for updates...", + "main.restoreLog": "↩️ All selected features have been restored.", + "main.restoreMessage": "This will restore all selected features to their original state.\nChanges made by previous configurations may be reverted.\n\nAre you sure you want to proceed?", + "main.restoreTitle": "Restore Selected Features", + "main.tabApplications": "Applications", + "main.tabWindows": "Windows", + "main.tooltipGithub": "Click here to visit the CrapFixer website at github.com/builtbybel/crapfixer", + "main.tooltipSupport": "Love CrapFixer? It's open source - but your support keeps it alive!", + "options.about": "About", + "options.features": "Features", + "options.plugins": "Plugins", + "options.settings": "Settings", + "plugins.colInstalled": "Installed", + "plugins.colPlugin": "Plugin", + "plugins.colType": "Type", + "plugins.description": "Plugins Gallery (App restart needed after install)", + "plugins.edit": "Edit", + "plugins.help": "Help", + "plugins.install": "Install", + "plugins.msgAllUpdated": "All plugins updated.", + "plugins.msgDownloadFailed": "Failed to download {0}: {1}", + "plugins.msgInfoTitle": "Info: {0}", + "plugins.msgLoadError": "Error loading plugins: ", + "plugins.msgNoneSelected": "No plugins selected.", + "plugins.msgOpenPluginFailed": "Could not open plugin: ", + "plugins.msgPluginNotFound": "Plugin file not found. Please install the plugin first.", + "plugins.msgRemoved": "Selected plugins removed.", + "plugins.msgSelectFirst": "Please select a plugin first.", + "plugins.msgSelectForDownload": "Please check one or more plugins to download.", + "plugins.remove": "Remove", + "plugins.search": "Search", + "plugins.stateNo": "No", + "plugins.stateUpdated": "Updated", + "plugins.stateYes": "Yes", + "plugins.submit": "Submit Plugin", + "plugins.typeOther": "Other", + "plugins.typePowershell": "Powershell", + "plugins.updateAll": "Update All", + "plugins.usage": "Usage notes", + "settings.iconDownloadFailed": "An error occurred while downloading the icons:\n", + "settings.iconDownloadFailedTitle": "Download Failed", + "settings.iconInstalled": "All icons have been successfully installed in the 'icons' folder!\n\nLove CrapFixer? Consider supporting me with a small donation to keep this tool alive and improving!", + "settings.iconInstalledTitle": "Icons Installed", + "settings.iconPrompt": "By default, buttons have no icons to reduce app size. Enable this to download and display navigation icons.\nWould you like to install it now?", + "settings.iconPromptTitle": "Icons Pack Detected", + "settings.installIcons": "Download optional icons to enhance navigation UI", + "settings.languageLabel": "Language", + "settings.restartPrompt": "Language has changed. Restart now to apply it?", + "settings.restartTitle": "Restart Required", + "settings.saveIni": "Save all settings to INI file", + "settings.sectionBasic": "Basic settings", + "settings.superPlugins": "Activate Plugins for PowerShell Tooling (Super Plugins)", + "vive.applyCustom": "Apply Custom", + "vive.applySelected": "Apply selected", + "vive.colFeature": "Feature", + "vive.colId": "ID", + "vive.colInfo": "Info", + "vive.colStatus": "Status", + "vive.customIds": "Custom IDs", + "vive.description": "ViVe Tool", + "vive.descriptionDisabled": "Enable experimental and hidden features (Disabled)", + "vive.descriptionEnabled": "Enable experimental and hidden features (Enabled)", + "vive.feature.drag_tray_share_ui": "Enable Drag Tray Share UI", + "vive.feature.snipping_tool_text_extractor": "Enable Text extractor in Snipping Tool", + "vive.feature.windows11_redesigned_start_menu": "Enable the redesigned Windows 11 Start menu", + "vive.moreInfos": "More infos", + "vive.msgConfirm": "Do you want to ENABLE these features?\n\n{0}\n\nYes = Enable\nNo = Disable\nCancel = Abort", + "vive.msgConfirmTitle": "Confirm Action", + "vive.msgCustomSent": "Custom feature action sent to ViVeTool.", + "vive.msgDoneTitle": "Done", + "vive.msgEnterIds": "Please enter one or more feature IDs.", + "vive.msgErrorTitle": "Error", + "vive.msgFeaturesApplied": "Features have been applied.", + "vive.msgInvalidId": "Invalid ID: '{0}'", + "vive.msgInvalidInputTitle": "Invalid Input", + "vive.msgNoValidIds": "No valid IDs found.", + "vive.msgOpenLinkFailed": "Failed to open link:\n{0}", + "vive.msgToolMissing": "ViVeTool not found. Please ensure it is installed in the plugins folder.", + "vive.msgUsage": "This plugin uses ViVeTool to enable hidden Windows features.\nPlease download ViVeTool (e.g. 'ViVeTool-v0.3.x-IntelAmd') from:\nhttps://github.com/thebookisclosed/ViVe/releases\nExtract it and place the contents into a subfolder inside the 'plugins' directory.\n", + "vive.statusAllEnabled": "All Enabled", + "vive.statusDisabled": "Disabled", + "vive.statusPartiallyEnabled": "Partially Enabled", + "vive.statusUnknown": "Unknown" +} + diff --git a/CFixer/lang/zh-CN.json b/CFixer/lang/zh-CN.json new file mode 100644 index 0000000..7bf58cd --- /dev/null +++ b/CFixer/lang/zh-CN.json @@ -0,0 +1,184 @@ +{ + "__meta.languageName": "中文(简体)", + "about.copyright": "版权所有 (c) 2025 A Belim app creation", + "about.description": "你可以在以下 GitHub 页面下载最新版本、提交问题并提出功能建议。", + "about.donate": "赞助", + "about.donationPurpose": "支持 CrapFixer 应用持续开发。", + "about.donationValidation": "请选择金额和币种。", + "common.chinese": "中文(简体)", + "common.english": "英文", + "common.language": "语言", + "feature.category.Ads": "广告", + "feature.category.AI": "AI", + "feature.category.Gaming": "游戏", + "feature.category.Issues": "问题修复", + "feature.category.MS Edge": "Edge 浏览器", + "feature.category.Privacy": "隐私", + "feature.category.System": "系统", + "feature.category.UI": "界面", + "feature.name.ads.disable_file_explorer": "禁用文件资源管理器广告", + "feature.name.ads.disable_finish_setup": "禁用完成设置广告", + "feature.name.ads.disable_lock_screen_tips": "禁用锁屏提示和广告", + "feature.name.ads.disable_personalized": "禁用个性化广告", + "feature.name.ads.disable_settings": "禁用设置广告", + "feature.name.ads.disable_start_menu": "禁用开始菜单广告", + "feature.name.ads.disable_tailored_experiences": "禁用个性化体验", + "feature.name.ads.disable_tips_suggestions": "禁用常规提示和广告", + "feature.name.ads.disable_welcome_experience": "禁用欢迎体验广告", + "feature.name.ai.disable_click_to_do": "禁用 Click to Do(仅 Copilot + PCs)", + "feature.name.ai.disable_recall": "关闭 Windows 11 Recall", + "feature.name.ai.hide_taskbar_copilot": "不在任务栏显示 Copilot", + "feature.name.ai.remove_ask_copilot_context_menu": "从右键菜单移除 Ask Copilot", + "feature.name.edge.disable_browser_signin_sync": "禁用浏览器登录与同步服务", + "feature.name.edge.disable_collections_access": "禁用 Collections 功能访问", + "feature.name.edge.disable_copilot_symbol": "禁用 Edge 中的 Copilot 图标", + "feature.name.edge.disable_cross_browser_import_on_launch": "禁止每次启动时从其他浏览器导入数据", + "feature.name.edge.disable_default_browser_setting": "禁用 Microsoft Edge 默认浏览器设置", + "feature.name.edge.disable_gamer_mode": "禁用游戏模式", + "feature.name.edge.disable_shopping_assistant": "禁用购物助手", + "feature.name.edge.disable_startup_boost": "禁用启动加速", + "feature.name.edge.disable_user_feedback_submit": "禁用提交用户反馈选项", + "feature.name.edge.hide_first_run_experience": "不显示首次运行体验", + "feature.name.edge.hide_new_tab_quick_links": "不在新标签页显示快速链接", + "feature.name.edge.hide_new_tab_sponsored_links": "不在新标签页显示赞助链接", + "feature.name.gaming.disable_game_dvr": "禁用游戏录制", + "feature.name.gaming.disable_power_throttling": "禁用电源限制", + "feature.name.gaming.disable_visual_effects": "禁用视觉效果", + "feature.name.issues.disk_cleanup_basic": "基础磁盘清理", + "feature.name.issues.upgrade_all_apps_winget": "Winget 应用更新", + "feature.name.privacy.disable_activity_history": "禁用活动历史记录", + "feature.name.privacy.disable_location_tracking": "禁用位置跟踪", + "feature.name.privacy.disable_signin_privacy_experience": "禁用登录时隐私设置体验", + "feature.name.privacy.disable_telemetry": "关闭遥测数据收集", + "feature.name.system.disable_network_throttling": "禁用网络限流", + "feature.name.system.enable_logon_verbose_status": "启用详细登录状态消息", + "feature.name.system.enable_taskbar_end_task": "启用结束任务", + "feature.name.system.optimize_responsiveness": "优化系统响应性", + "feature.name.system.reduce_menu_show_delay": "加快菜单弹出速度", + "feature.name.system.show_bsod_details": "显示蓝屏详细信息(替代简化哭脸)", + "feature.name.system.speed_up_shutdown": "加快关机速度", + "feature.name.ui.align_taskbar_start_left": "任务栏靠左对齐", + "feature.name.ui.disable_bing_search": "禁用必应搜索", + "feature.name.ui.disable_personalized_lock_screen": "不使用个性化锁屏", + "feature.name.ui.disable_search_box_suggestions": "禁用搜索框建议", + "feature.name.ui.disable_snap_assist_flyout": "禁用贴靠窗口", + "feature.name.ui.disable_transparency_effects": "禁用透明效果", + "feature.name.ui.enable_dark_mode_apps": "为应用启用深色模式", + "feature.name.ui.enable_dark_mode_system": "为系统启用深色模式", + "feature.name.ui.enable_start_menu_more_pins": "开始菜单显示更多已固定应用", + "feature.name.ui.hide_start_menu_most_used_apps": "隐藏开始菜单常用应用", + "feature.name.ui.hide_taskbar_search_box": "隐藏任务栏搜索框", + "feature.name.ui.hide_taskbar_task_view_button": "隐藏任务栏任务视图按钮", + "feature.name.ui.show_windows11_full_context_menu": "在 Windows 11 显示完整右键菜单", + "main.appsPlaceholder": "尚未分析", + "main.btnAnalyze": "分析(&A)", + "main.btnFix": "执行修复(&F)", + "main.btnFixer": "修复器(&F)", + "main.btnRestore": "还原(&R)", + "main.btnTools": "工具(&T)", + "main.ctxAnalyze": "分析", + "main.ctxFix": "修复", + "main.ctxHelp": "帮助", + "main.ctxRestore": "还原", + "main.linkSelection": "全选", + "main.linkUpdate": "检查更新...", + "main.restoreLog": "↩️ 已还原所有已选择的功能。", + "main.restoreMessage": "这将把所选功能恢复为初始状态。\n之前配置带来的更改可能会被撤销。\n\n确定要继续吗?", + "main.restoreTitle": "还原所选功能", + "main.tabApplications": "应用程序", + "main.tabWindows": "系统功能", + "main.tooltipGithub": "点击访问 CrapFixer 项目主页:github.com/builtbybel/crapfixer", + "main.tooltipSupport": "喜欢 CrapFixer 吗?它是开源项目,你的支持会让它持续更新。", + "options.about": "关于", + "options.features": "功能", + "options.plugins": "插件", + "options.settings": "设置", + "plugins.colInstalled": "已安装", + "plugins.colPlugin": "插件", + "plugins.colType": "类型", + "plugins.description": "插件库(安装后需要重启应用)", + "plugins.edit": "编辑", + "plugins.help": "帮助", + "plugins.install": "安装", + "plugins.msgAllUpdated": "所有插件已更新。", + "plugins.msgDownloadFailed": "下载失败 {0}: {1}", + "plugins.msgInfoTitle": "信息:{0}", + "plugins.msgLoadError": "加载插件失败:", + "plugins.msgNoneSelected": "未选择任何插件。", + "plugins.msgOpenPluginFailed": "无法打开插件:", + "plugins.msgPluginNotFound": "未找到插件文件,请先安装该插件。", + "plugins.msgRemoved": "已移除所选插件。", + "plugins.msgSelectFirst": "请先选择一个插件。", + "plugins.msgSelectForDownload": "请至少勾选一个要下载的插件。", + "plugins.remove": "移除", + "plugins.search": "搜索", + "plugins.stateNo": "否", + "plugins.stateUpdated": "已更新", + "plugins.stateYes": "是", + "plugins.submit": "提交插件", + "plugins.typeOther": "其他", + "plugins.typePowershell": "PowerShell 脚本", + "plugins.name.ChrisTitusApp": "ChrisTitusApp", + "plugins.name.Create Restore Point": "创建还原点", + "plugins.name.Remove default apps": "移除默认应用", + "plugins.name.Remove Windows AI": "移除 Windows AI", + "plugins.name.Restart Explorer": "重启资源管理器", + "plugins.name.Restore all built-in apps": "恢复所有内置应用", + "plugins.name.Uninstall OneDrive": "卸载 OneDrive", + "plugins.name.CFEnhancer": "CFEnhancer", + "plugins.name.Disable Snap Assist Flyout (NX)": "禁用贴靠窗口提醒(NX)", + "plugins.name.File Extensions Visibility (NX)": "文件扩展名可见性(NX)", + "plugins.name.Shutdown Time (NX)": "关机时间(NX)", + "plugins.name.User Account Control (NX)": "用户账户控制(NX)", + "plugins.name.Remove Edit with Clipchamp (NX)": "移除“使用 Clipchamp 编辑”(NX)", + "plugins.name.Remove Edit with Notepad (NX)": "移除“使用记事本编辑”(NX)", + "plugins.name.Remove Edit with Photos (NX)": "移除“使用照片编辑”(NX)", + "plugins.name.Remove Ask Copilot (NX)": "移除“询问 Copilot”(NX)", + "plugins.updateAll": "全部更新", + "plugins.usage": "使用说明", + "settings.iconDownloadFailed": "下载图标时发生错误:\n", + "settings.iconDownloadFailedTitle": "下载失败", + "settings.iconInstalled": "所有图标已成功安装到 'icons' 文件夹。\n\n如果你喜欢 CrapFixer,欢迎小额赞助支持持续开发。", + "settings.iconInstalledTitle": "图标安装完成", + "settings.iconPrompt": "默认不带图标以减小体积。启用后会下载并显示导航图标。\n现在安装吗?", + "settings.iconPromptTitle": "检测到图标包", + "settings.installIcons": "下载可选图标以增强导航界面", + "settings.languageLabel": "语言", + "settings.restartPrompt": "语言已更改,是否立即重启应用以生效?", + "settings.restartTitle": "需要重启", + "settings.saveIni": "将所有设置保存到 INI 文件", + "settings.sectionBasic": "基础设置", + "settings.superPlugins": "启用 PowerShell 插件支持(Super Plugins)", + "vive.applyCustom": "应用自定义", + "vive.applySelected": "应用所选", + "vive.colFeature": "功能", + "vive.colId": "ID", + "vive.colInfo": "信息", + "vive.colStatus": "状态", + "vive.customIds": "自定义 ID", + "vive.description": "ViVe 工具", + "vive.descriptionDisabled": "启用实验/隐藏功能(未启用)", + "vive.descriptionEnabled": "启用实验/隐藏功能(已启用)", + "vive.feature.drag_tray_share_ui": "启用拖拽托盘分享界面", + "vive.feature.snipping_tool_text_extractor": "启用截图工具文本提取器", + "vive.feature.windows11_redesigned_start_menu": "启用 Windows 11 全新开始菜单", + "vive.moreInfos": "更多信息", + "vive.msgConfirm": "是否要启用这些功能?\n\n{0}\n\n是 = 启用\n否 = 禁用\n取消 = 终止", + "vive.msgConfirmTitle": "确认操作", + "vive.msgCustomSent": "已向 ViVeTool 发送自定义功能操作。", + "vive.msgDoneTitle": "完成", + "vive.msgEnterIds": "请输入一个或多个功能 ID。", + "vive.msgErrorTitle": "错误", + "vive.msgFeaturesApplied": "功能已应用。", + "vive.msgInvalidId": "无效 ID:'{0}'", + "vive.msgInvalidInputTitle": "输入无效", + "vive.msgNoValidIds": "未找到有效 ID。", + "vive.msgOpenLinkFailed": "打开链接失败:\n{0}", + "vive.msgToolMissing": "未找到 ViVeTool,请确认它已安装到 plugins 文件夹中。", + "vive.msgUsage": "该插件使用 ViVeTool 启用 Windows 隐藏功能。\n请从以下地址下载 ViVeTool(例如 'ViVeTool-v0.3.x-IntelAmd'):\nhttps://github.com/thebookisclosed/ViVe/releases\n解压后放入 'plugins' 目录下的子文件夹。\n", + "vive.statusAllEnabled": "全部启用", + "vive.statusDisabled": "已禁用", + "vive.statusPartiallyEnabled": "部分启用", + "vive.statusUnknown": "未知" +} +