From eaff583580965448cb5752f33bdd6f7f5c5cd3fa Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Tue, 2 Jun 2026 14:32:44 +0900 Subject: [PATCH 1/4] feat: add TemperatureUnit enum and localized strings Introduce TemperatureUnit (System/Celsius/Fahrenheit) with extension methods for string conversion, localization, and resolution against the OS region default. Add Menu_TemperatureUnit and TemperatureUnit_* keys to all seven language resources. Nothing references the enum yet; the build remains green. Co-Authored-By: Claude Opus 4.7 (1M context) --- RunCat365/Properties/Strings.Designer.cs | 24 ++++++++ RunCat365/Properties/Strings.de.resx | 12 ++++ RunCat365/Properties/Strings.es.resx | 12 ++++ RunCat365/Properties/Strings.fr.resx | 12 ++++ RunCat365/Properties/Strings.ja.resx | 12 ++++ RunCat365/Properties/Strings.resx | 12 ++++ RunCat365/Properties/Strings.zh-CN.resx | 12 ++++ RunCat365/Properties/Strings.zh-TW.resx | 12 ++++ RunCat365/TemperatureUnit.cs | 72 ++++++++++++++++++++++++ 9 files changed, 180 insertions(+) create mode 100644 RunCat365/TemperatureUnit.cs diff --git a/RunCat365/Properties/Strings.Designer.cs b/RunCat365/Properties/Strings.Designer.cs index c18ea1e..3b8db2c 100644 --- a/RunCat365/Properties/Strings.Designer.cs +++ b/RunCat365/Properties/Strings.Designer.cs @@ -503,5 +503,29 @@ internal static string CustomRunner_PreviewSpeed { return ResourceManager.GetString("CustomRunner_PreviewSpeed", resourceCulture); } } + + internal static string Menu_TemperatureUnit { + get { + return ResourceManager.GetString("Menu_TemperatureUnit", resourceCulture); + } + } + + internal static string TemperatureUnit_System { + get { + return ResourceManager.GetString("TemperatureUnit_System", resourceCulture); + } + } + + internal static string TemperatureUnit_Celsius { + get { + return ResourceManager.GetString("TemperatureUnit_Celsius", resourceCulture); + } + } + + internal static string TemperatureUnit_Fahrenheit { + get { + return ResourceManager.GetString("TemperatureUnit_Fahrenheit", resourceCulture); + } + } } } diff --git a/RunCat365/Properties/Strings.de.resx b/RunCat365/Properties/Strings.de.resx index 8caa200..41a13c4 100644 --- a/RunCat365/Properties/Strings.de.resx +++ b/RunCat365/Properties/Strings.de.resx @@ -153,6 +153,18 @@ Dunkel + + Temperatureinheit + + + System + + + Celsius + + + Fahrenheit + Katze diff --git a/RunCat365/Properties/Strings.es.resx b/RunCat365/Properties/Strings.es.resx index 2210f94..2e16b4f 100644 --- a/RunCat365/Properties/Strings.es.resx +++ b/RunCat365/Properties/Strings.es.resx @@ -153,6 +153,18 @@ Oscuro + + Unidad de temperatura + + + Sistema + + + Celsius + + + Fahrenheit + Gato diff --git a/RunCat365/Properties/Strings.fr.resx b/RunCat365/Properties/Strings.fr.resx index b5328e2..eb9a23c 100644 --- a/RunCat365/Properties/Strings.fr.resx +++ b/RunCat365/Properties/Strings.fr.resx @@ -153,6 +153,18 @@ Sombre + + Unité de température + + + Système + + + Celsius + + + Fahrenheit + Chat diff --git a/RunCat365/Properties/Strings.ja.resx b/RunCat365/Properties/Strings.ja.resx index c9f2c33..12b8e56 100644 --- a/RunCat365/Properties/Strings.ja.resx +++ b/RunCat365/Properties/Strings.ja.resx @@ -153,6 +153,18 @@ ダーク + + 温度単位 + + + システム + + + 摂氏 + + + 華氏 + ネコ diff --git a/RunCat365/Properties/Strings.resx b/RunCat365/Properties/Strings.resx index 16ef79a..34aeace 100644 --- a/RunCat365/Properties/Strings.resx +++ b/RunCat365/Properties/Strings.resx @@ -153,6 +153,18 @@ Dark + + Temperature Unit + + + System + + + Celsius + + + Fahrenheit + Cat diff --git a/RunCat365/Properties/Strings.zh-CN.resx b/RunCat365/Properties/Strings.zh-CN.resx index 4114f13..b065ec5 100644 --- a/RunCat365/Properties/Strings.zh-CN.resx +++ b/RunCat365/Properties/Strings.zh-CN.resx @@ -153,6 +153,18 @@ 深色 + + 温度单位 + + + 系统 + + + 摄氏 + + + 华氏 + 猫咪 diff --git a/RunCat365/Properties/Strings.zh-TW.resx b/RunCat365/Properties/Strings.zh-TW.resx index 61d3e00..6e643a1 100644 --- a/RunCat365/Properties/Strings.zh-TW.resx +++ b/RunCat365/Properties/Strings.zh-TW.resx @@ -153,6 +153,18 @@ 深色 + + 溫度單位 + + + 系統 + + + 攝氏 + + + 華氏 + 貓咪 diff --git a/RunCat365/TemperatureUnit.cs b/RunCat365/TemperatureUnit.cs new file mode 100644 index 0000000..1feb828 --- /dev/null +++ b/RunCat365/TemperatureUnit.cs @@ -0,0 +1,72 @@ +// Copyright 2025 Takuto Nakamura +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using RunCat365.Properties; +using System.Globalization; + +namespace RunCat365 +{ + enum TemperatureUnit + { + System, + Celsius, + Fahrenheit, + } + + internal static class TemperatureUnitExtension + { + private static readonly TemperatureUnit systemDefault = DetectSystemDefault(); + + internal static string GetString(this TemperatureUnit unit) + { + return unit switch + { + TemperatureUnit.System => "System", + TemperatureUnit.Celsius => "Celsius", + TemperatureUnit.Fahrenheit => "Fahrenheit", + _ => "", + }; + } + + internal static string GetLocalizedString(this TemperatureUnit unit) + { + return unit switch + { + TemperatureUnit.System => Strings.TemperatureUnit_System, + TemperatureUnit.Celsius => Strings.TemperatureUnit_Celsius, + TemperatureUnit.Fahrenheit => Strings.TemperatureUnit_Fahrenheit, + _ => "", + }; + } + + internal static TemperatureUnit Resolve(this TemperatureUnit unit) + { + return unit == TemperatureUnit.System ? systemDefault : unit; + } + + private static TemperatureUnit DetectSystemDefault() + { + try + { + return new RegionInfo(CultureInfo.CurrentCulture.Name).IsMetric + ? TemperatureUnit.Celsius + : TemperatureUnit.Fahrenheit; + } + catch (ArgumentException) + { + return TemperatureUnit.Celsius; + } + } + } +} From fb24066631d3166e8a304e449e71d0fd7786139f Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Tue, 2 Jun 2026 14:33:31 +0900 Subject: [PATCH 2/4] refactor: thread TemperatureUnit through TemperatureInfoExtension Replace the static usesFahrenheit cache with an explicit TemperatureUnit parameter on GetDescription / GenerateIndicator. The resolution logic (System -> region default) now lives on TemperatureUnitExtension so the repository no longer carries process-wide state. Program.cs passes TemperatureUnit.System for now, preserving the existing behavior. Co-Authored-By: Claude Opus 4.7 (1M context) --- RunCat365/Program.cs | 4 ++-- RunCat365/TemperatureRepository.cs | 33 ++++++++++-------------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/RunCat365/Program.cs b/RunCat365/Program.cs index ed2ad54..1b8f6fe 100644 --- a/RunCat365/Program.cs +++ b/RunCat365/Program.cs @@ -283,7 +283,7 @@ private string GetInfoDescription(CPUInfo cpuInfo, GPUInfo? gpuInfo, MemoryInfo _ => "", }; - var temperatureDescription = temperatureInfo?.GetDescription() ?? ""; + var temperatureDescription = temperatureInfo?.GetDescription(TemperatureUnit.System) ?? ""; return string.IsNullOrEmpty(temperatureDescription) ? baseDescription : $"{baseDescription}\n{temperatureDescription}"; } @@ -320,7 +320,7 @@ private int FetchSystemInfo() systemInfoValues.AddRange(memoryInfo.GenerateIndicator()); if (temperatureInfo.HasValue) { - systemInfoValues.AddRange(temperatureInfo.Value.GenerateIndicator()); + systemInfoValues.AddRange(temperatureInfo.Value.GenerateIndicator(TemperatureUnit.System)); } systemInfoValues.AddRange(storageInfo.GenerateIndicator()); if (networkInfo.HasValue) diff --git a/RunCat365/TemperatureRepository.cs b/RunCat365/TemperatureRepository.cs index d251eb6..07f5f88 100644 --- a/RunCat365/TemperatureRepository.cs +++ b/RunCat365/TemperatureRepository.cs @@ -29,44 +29,33 @@ internal static class TemperatureInfoExtension private const float CELSIUS_TO_FAHRENHEIT_SCALE = 9.0f / 5.0f; private const float CELSIUS_TO_FAHRENHEIT_OFFSET = 32.0f; - private static readonly bool usesFahrenheit = UsesFahrenheit(); - - internal static string GetDescription(this TemperatureInfo temperatureInfo) + internal static string GetDescription(this TemperatureInfo temperatureInfo, TemperatureUnit unit) { - return $"{Strings.SystemInfo_Temperature}: {temperatureInfo.MaximumCelsius.ToLocalizedTemperatureText()}"; + var resolvedUnit = unit.Resolve(); + return $"{Strings.SystemInfo_Temperature}: {temperatureInfo.MaximumCelsius.ToLocalizedTemperatureText(resolvedUnit)}"; } - internal static List GenerateIndicator(this TemperatureInfo temperatureInfo) + internal static List GenerateIndicator(this TemperatureInfo temperatureInfo, TemperatureUnit unit) { + var resolvedUnit = unit.Resolve(); return [ TreeFormatter.CreateRoot($"{Strings.SystemInfo_Temperature}:"), - TreeFormatter.CreateNode($"{Strings.SystemInfo_Average}: {temperatureInfo.AverageCelsius.ToLocalizedTemperatureText()}", false), - TreeFormatter.CreateNode($"{Strings.SystemInfo_Maximum}: {temperatureInfo.MaximumCelsius.ToLocalizedTemperatureText()}", true) + TreeFormatter.CreateNode($"{Strings.SystemInfo_Average}: {temperatureInfo.AverageCelsius.ToLocalizedTemperatureText(resolvedUnit)}", false), + TreeFormatter.CreateNode($"{Strings.SystemInfo_Maximum}: {temperatureInfo.MaximumCelsius.ToLocalizedTemperatureText(resolvedUnit)}", true) ]; } - private static string ToLocalizedTemperatureText(this float temperatureCelsius) + private static string ToLocalizedTemperatureText(this float temperatureCelsius, TemperatureUnit resolvedUnit) { - var value = usesFahrenheit + var useFahrenheit = resolvedUnit == TemperatureUnit.Fahrenheit; + var value = useFahrenheit ? temperatureCelsius * CELSIUS_TO_FAHRENHEIT_SCALE + CELSIUS_TO_FAHRENHEIT_OFFSET : temperatureCelsius; - var format = usesFahrenheit + var format = useFahrenheit ? Strings.SystemInfo_TemperatureFahrenheitFormat : Strings.SystemInfo_TemperatureCelsiusFormat; return string.Format(CultureInfo.CurrentCulture, format, value); } - - private static bool UsesFahrenheit() - { - try - { - return !new RegionInfo(CultureInfo.CurrentCulture.Name).IsMetric; - } - catch (ArgumentException) - { - return false; - } - } } internal class TemperaturePerformanceCounters From 27ad707d0b1c6788426eb1e825fa8d4faef8ff80 Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Tue, 2 Jun 2026 14:35:14 +0900 Subject: [PATCH 3/4] feat: expose TemperatureUnit setting in the context menu Persist the chosen unit in UserSettings (empty default falls back to TemperatureUnit.System), wire it through Program.cs, and add a "Temperature Unit" submenu next to Theme. With System the indicator keeps following the OS region default; Celsius/Fahrenheit override it explicitly. Updates appear on the next fetch tick. Co-Authored-By: Claude Opus 4.7 (1M context) --- RunCat365/ContextMenuManager.cs | 19 +++++++++++++++++++ RunCat365/Program.cs | 15 +++++++++++++-- RunCat365/Properties/UserSettings.Designer.cs | 14 +++++++++++++- RunCat365/Properties/UserSettings.settings | 3 +++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/RunCat365/ContextMenuManager.cs b/RunCat365/ContextMenuManager.cs index f0fa06e..38f5c2c 100644 --- a/RunCat365/ContextMenuManager.cs +++ b/RunCat365/ContextMenuManager.cs @@ -43,6 +43,8 @@ internal ContextMenuManager( Func isSpeedSourceAvailable, Func getFPSMaxLimit, Action setFPSMaxLimit, + Func getTemperatureUnit, + Action setTemperatureUnit, Func getLaunchAtStartup, Func toggleLaunchAtStartup, Action openProjectPage, @@ -127,6 +129,22 @@ Action onExit _ => null ); + var temperatureUnitMenu = new CustomToolStripMenuItem(Strings.Menu_TemperatureUnit); + temperatureUnitMenu.SetupSubMenusFromEnum( + u => u.GetLocalizedString(), + (parent, sender, e) => + { + HandleMenuItemSelection( + parent, + sender, + (string? s, out TemperatureUnit u) => Enum.TryParse(s, out u), + u => setTemperatureUnit(u) + ); + }, + u => getTemperatureUnit() == u, + _ => null + ); + var launchAtStartupMenu = new CustomToolStripMenuItem(Strings.Menu_LaunchAtStartup) { Checked = getLaunchAtStartup() @@ -138,6 +156,7 @@ Action onExit themeMenu, speedSourceMenu, fpsMaxLimitMenu, + temperatureUnitMenu, launchAtStartupMenu ); diff --git a/RunCat365/Program.cs b/RunCat365/Program.cs index 1b8f6fe..85931c4 100644 --- a/RunCat365/Program.cs +++ b/RunCat365/Program.cs @@ -67,6 +67,7 @@ internal class RunCat365ApplicationContext : ApplicationContext private readonly FormsTimer animateTimer; private Runner runner = Runner.Cat; private Theme manualTheme = Theme.System; + private TemperatureUnit temperatureUnit = TemperatureUnit.System; private FPSMaxLimit fpsMaxLimit = FPSMaxLimit.FPS40; private SpeedSource speedSource = SpeedSource.CPU; private string? customRunnerName; @@ -78,6 +79,7 @@ public RunCat365ApplicationContext() UserSettings.Default.Reload(); _ = Enum.TryParse(UserSettings.Default.Runner, out runner); _ = Enum.TryParse(UserSettings.Default.Theme, out manualTheme); + _ = Enum.TryParse(UserSettings.Default.TemperatureUnit, out temperatureUnit); _ = Enum.TryParse(UserSettings.Default.FPSMaxLimit, out fpsMaxLimit); _ = Enum.TryParse(UserSettings.Default.SpeedSource, out speedSource); customRunnerName = string.IsNullOrEmpty(UserSettings.Default.CustomRunnerName) @@ -112,6 +114,8 @@ public RunCat365ApplicationContext() s => IsSpeedSourceAvailable(s), () => fpsMaxLimit, f => ChangeFPSMaxLimit(f), + () => temperatureUnit, + u => ChangeTemperatureUnit(u), () => launchAtStartupManager.GetStartup(), s => launchAtStartupManager.ToggleStartup(s), () => OpenProjectPage(), @@ -254,6 +258,13 @@ private void ChangeManualTheme(Theme t) UserSettings.Default.Save(); } + private void ChangeTemperatureUnit(TemperatureUnit u) + { + temperatureUnit = u; + UserSettings.Default.TemperatureUnit = temperatureUnit.ToString(); + UserSettings.Default.Save(); + } + private void ChangeSpeedSource(SpeedSource s) { speedSource = s; @@ -283,7 +294,7 @@ private string GetInfoDescription(CPUInfo cpuInfo, GPUInfo? gpuInfo, MemoryInfo _ => "", }; - var temperatureDescription = temperatureInfo?.GetDescription(TemperatureUnit.System) ?? ""; + var temperatureDescription = temperatureInfo?.GetDescription(temperatureUnit) ?? ""; return string.IsNullOrEmpty(temperatureDescription) ? baseDescription : $"{baseDescription}\n{temperatureDescription}"; } @@ -320,7 +331,7 @@ private int FetchSystemInfo() systemInfoValues.AddRange(memoryInfo.GenerateIndicator()); if (temperatureInfo.HasValue) { - systemInfoValues.AddRange(temperatureInfo.Value.GenerateIndicator(TemperatureUnit.System)); + systemInfoValues.AddRange(temperatureInfo.Value.GenerateIndicator(temperatureUnit)); } systemInfoValues.AddRange(storageInfo.GenerateIndicator()); if (networkInfo.HasValue) diff --git a/RunCat365/Properties/UserSettings.Designer.cs b/RunCat365/Properties/UserSettings.Designer.cs index ae8ad4a..c22b676 100644 --- a/RunCat365/Properties/UserSettings.Designer.cs +++ b/RunCat365/Properties/UserSettings.Designer.cs @@ -46,7 +46,19 @@ public string Theme { this["Theme"] = value; } } - + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string TemperatureUnit { + get { + return ((string)(this["TemperatureUnit"])); + } + set { + this["TemperatureUnit"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("FPS40")] diff --git a/RunCat365/Properties/UserSettings.settings b/RunCat365/Properties/UserSettings.settings index f7b5a60..57c0c84 100644 --- a/RunCat365/Properties/UserSettings.settings +++ b/RunCat365/Properties/UserSettings.settings @@ -8,6 +8,9 @@ + + + FPS40 From 9c6d3b3515e63aa34faf4bcb5b385f659316bfac Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Tue, 2 Jun 2026 16:27:11 +0900 Subject: [PATCH 4/4] chore: drop unused GetString from Theme and TemperatureUnit ThemeExtension.GetString was last used by SetIcons before localization and icon-name refactors moved every caller off it. The TemperatureUnit version was copied from the Theme template and never had a caller. Remove both so the extensions only carry the methods that are actually used (GetLocalizedString, Resolve, GetContrastColor). Co-Authored-By: Claude Opus 4.7 (1M context) --- RunCat365/TemperatureUnit.cs | 11 ----------- RunCat365/Theme.cs | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/RunCat365/TemperatureUnit.cs b/RunCat365/TemperatureUnit.cs index 1feb828..2b3dbcb 100644 --- a/RunCat365/TemperatureUnit.cs +++ b/RunCat365/TemperatureUnit.cs @@ -28,17 +28,6 @@ internal static class TemperatureUnitExtension { private static readonly TemperatureUnit systemDefault = DetectSystemDefault(); - internal static string GetString(this TemperatureUnit unit) - { - return unit switch - { - TemperatureUnit.System => "System", - TemperatureUnit.Celsius => "Celsius", - TemperatureUnit.Fahrenheit => "Fahrenheit", - _ => "", - }; - } - internal static string GetLocalizedString(this TemperatureUnit unit) { return unit switch diff --git a/RunCat365/Theme.cs b/RunCat365/Theme.cs index fd6d560..05fca6e 100644 --- a/RunCat365/Theme.cs +++ b/RunCat365/Theme.cs @@ -25,17 +25,6 @@ enum Theme internal static class ThemeExtension { - internal static string GetString(this Theme theme) - { - return theme switch - { - Theme.System => "System", - Theme.Light => "Light", - Theme.Dark => "Dark", - _ => "", - }; - } - internal static string GetLocalizedString(this Theme theme) { return theme switch