diff --git a/program/Language.cs b/program/Language.cs index 70695ae..a9afd86 100644 --- a/program/Language.cs +++ b/program/Language.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using Gtk; public class Language { - public static bool LanguageSwitchValue = false; - private const string EngCssPath = "lang_eng.css"; private const string JpnCssPath = "lang_jpn.css"; private const string EngCsvPath = "info_eng.csv"; @@ -14,84 +13,134 @@ private readonly Dictionary _engText; private readonly Dictionary _jpnText; - private readonly Dictionary _buttonsById; - public readonly CssProvider _languageCssProvider = new CssProvider(); - private readonly Label _languageLabel; + private Dictionary _text; - public Language(Dictionary buttonsById, Label languageLabel) + // widgets whose label is a plain CSV lookup + private readonly Dictionary _widgets = new Dictionary(); + + public readonly CssProvider _languageCssProvider = new CssProvider(); + + public bool IsEnglish { get; private set; } + + /// Raised after every language change, so callers can rebuild state-dependent text. + public event EventHandler Changed; + + public Language(bool isEnglish) { - _buttonsById = buttonsById; - _languageLabel = languageLabel; StyleContext.AddProviderForScreen(Gdk.Screen.Default, _languageCssProvider, 600); + _engText = LoadText(EngCsvPath); + _jpnText = LoadText(JpnCsvPath); - _engText = LoadButtonText(EngCsvPath); - _jpnText = LoadButtonText(JpnCsvPath); + // set before any widget is built, so ctor-time lookups are already correct + IsEnglish = isEnglish; + _text = isEnglish ? _engText : _jpnText; } - private Dictionary LoadButtonText(string path) + // ---------- registration ---------- + + /// Label comes straight from the CSV key. Applied automatically on every change. + public void Register(string key, Widget widget) + { + if (widget == null) return; + widget.Name = key; // enables #Key selectors in the lang CSS + _widgets[key] = widget; + } + + /// Widget whose text the owner sets itself (state-dependent). Name only, no auto-apply. + public void RegisterDynamic(string key, Widget widget) + { + if (widget != null) widget.Name = key; + } + + // ---------- lookup ---------- + + public string this[string key] { get { return Get(key); } } + + public string Get(string key) + { + string value; + if (_text.TryGetValue(key, out value)) return value; + + Console.WriteLine("Warning: missing text key '" + key + "'"); + return key; + } + + public string Format(string key, params object[] args) + { + string template = Get(key); + try + { + return string.Format(CultureInfo.InvariantCulture, template, args); + } + catch (FormatException) + { + Console.WriteLine("Warning: bad placeholder in text key '" + key + "'"); + return template; + } + } + + // ---------- apply ---------- + + public void Apply(bool isEnglish) + { + IsEnglish = isEnglish; + _text = isEnglish ? _engText : _jpnText; + + foreach (var kvp in _widgets) + { + string value; + if (_text.TryGetValue(kvp.Key, out value)) + SetText(kvp.Value, value); + else + Console.WriteLine("Warning: missing text key '" + kvp.Key + "'"); + } + + string cssPath = isEnglish ? EngCssPath : JpnCssPath; + if (File.Exists(cssPath)) + _languageCssProvider.LoadFromPath(cssPath); + else + Console.WriteLine("Warning: CSS file not found: " + cssPath); + + EventHandler handler = Changed; + if (handler != null) handler(this, EventArgs.Empty); + } + + private static void SetText(Widget widget, string value) + { + Button button = widget as Button; // also covers ToggleButton + if (button != null) { button.Label = value; return; } + + Label label = widget as Label; + if (label != null) { label.Text = value; return; } + + Console.WriteLine("Warning: don't know how to set text on " + widget.GetType().Name); + } + + // ---------- csv ---------- + + private Dictionary LoadText(string path) { var result = new Dictionary(); + if (!File.Exists(path)) { - Console.WriteLine($"Warning: text file not found: {path}"); + Console.WriteLine("Warning: text file not found: " + path); return result; } - var lines = File.ReadAllLines(path); - for (int i = 1; i < lines.Length; i++) + string[] lines = File.ReadAllLines(path); + for (int i = 1; i < lines.Length; i++) // row 0 is the header { - var line = lines[i].Trim(); - if (string.IsNullOrEmpty(line)) continue; - var parts = line.Split(new[] { ',' }, 2); + string line = lines[i].Trim(); + if (string.IsNullOrEmpty(line) || line.StartsWith("#")) continue; + + string[] parts = line.Split(new[] { ',' }, 2); if (parts.Length < 2) continue; - result[parts[0]] = parts[1]; + + result[parts[0].Trim()] = parts[1]; // value keeps any commas } + return result; } - - public void ApplyLanguage(bool isEnglish) - { - var text = isEnglish ? _engText : _jpnText; - string cssPath = isEnglish ? EngCssPath : JpnCssPath; - - foreach (var kvp in _buttonsById) - if (text.TryGetValue(kvp.Key, out var buttonText)) - kvp.Value.Label = buttonText; - - if (File.Exists(cssPath)) - _languageCssProvider.LoadFromPath(cssPath); - else - Console.WriteLine($"Warning: CSS file not found: {cssPath}"); - - _languageLabel.Text = isEnglish ? "あ" : "A"; - } - - //for others - public Language(Dictionary buttonsById) - { - _buttonsById = buttonsById; - StyleContext.AddProviderForScreen(Gdk.Screen.Default, _languageCssProvider, 600); - - - _engText = LoadButtonText(EngCsvPath); - _jpnText = LoadButtonText(JpnCsvPath); - } - - - public void ApplyLanguages(bool isEnglish) - { - var text = isEnglish ? _engText : _jpnText; - string cssPath = isEnglish ? EngCssPath : JpnCssPath; - - foreach (var kvp in _buttonsById) - if (text.TryGetValue(kvp.Key, out var buttonText)) - kvp.Value.Label = buttonText; - - if (File.Exists(cssPath)) - _languageCssProvider.LoadFromPath(cssPath); - else - Console.WriteLine($"Warning: CSS file not found: {cssPath}"); - - } } \ No newline at end of file diff --git a/program/MainWindow.cs b/program/MainWindow.cs index e87516b..b68f449 100644 --- a/program/MainWindow.cs +++ b/program/MainWindow.cs @@ -38,7 +38,8 @@ // private Dictionary _engText; // private Dictionary _jpnText; - private Dictionary _buttonsById; + // private Dictionary _buttonsById; + private bool _connected; private readonly ArduinoConnection _arduino = new ArduinoConnection(); internal SettingsWindow _settingsWindow; @@ -84,31 +85,46 @@ _arduino.ConnectionChanged += OnArduinoConnectionChanged; - _buttonsById = new Dictionary - { - { "Option1Button", Option1Button }, - { "Option2Button", Option2Button }, - { "Option3Button", Option3Button }, - { "CloseButton", CloseButton }, - { "SettingButton", SettingButton }, - }; - // CSS ID selectors (#Option1Button etc.) need the widget Name set explicitly - foreach (var kvp in _buttonsById) - kvp.Value.Name = kvp.Key; - // _engText = LoadButtonText("info_eng.csv"); - // _jpnText = LoadButtonText("info_jpn.csv"); + // // _buttonsById = new Dictionary + // // { + // // { "Option1Button", Option1Button }, + // // { "Option2Button", Option2Button }, + // // { "Option3Button", Option3Button }, + // // { "CloseButton", CloseButton }, + // // { "SettingButton", SettingButton }, + // // }; - _Language = new Language(_buttonsById,LanguageLabel); + // // CSS ID selectors (#Option1Button etc.) need the widget Name set explicitly + // foreach (var kvp in _buttonsById) + // kvp.Value.Name = kvp.Key; - // _Language.ApplyLanguage(LanguageSwitch.Active); - - _settingsWindow = new SettingsWindow(builder, _arduino); + // // _engText = LoadButtonText("info_eng.csv"); + // // _jpnText = LoadButtonText("info_jpn.csv"); + + // _Language = new Language(_buttonsById,LanguageLabel); + + // // _Language.ApplyLanguage(LanguageSwitch.Active); + + _Language = new Language(LanguageToggle.Active); + _Language.Register("Option1Button", Option1Button); + _Language.Register("Option2Button", Option2Button); + _Language.Register("Option3Button", Option3Button); + _Language.Register("CloseButton", CloseButton); + _Language.Register("SettingButton", SettingButton); + _Language.Register("LanguageLabel", LanguageLabel); + _Language.Register("LanguageToggle", LanguageToggle); + _Language.RegisterDynamic("ConnectionIcon", ConnectionIcon); + + _settingsWindow = new SettingsWindow(builder, _arduino, _Language); _settingsWindow.BackRequested += OnSettingsClosed; - // single source of truth: the toggle drives both pages - ApplyLanguageEverywhere(LanguageToggle.Active); + _Language.Changed += (s, e) => OnArduinoConnectionChanged(_connected); + _Language.Apply(LanguageToggle.Active); + + // // single source of truth: the toggle drives both pages + // ApplyLanguageEverywhere(LanguageToggle.Active); RootNoteBook.ShowTabs = false; RootNoteBook.ShowBorder = false; @@ -147,6 +163,7 @@ private void OnArduinoConnectionChanged(bool connected) { + _connected = connected; if (ConnectionIcon == null) return; ConnectionIcon.SetFromIconName( @@ -154,36 +171,36 @@ IconSize.Dnd); ConnectionIcon.TooltipText = connected - ? $"Connected to {_arduino.PortName}" - : "Device not connected"; + ? _Language.Format("ConnectionIcon.Connected", _arduino.PortName) + : _Language["ConnectionIcon.Disconnected"]; } // ---------- localisation ---------- - private Dictionary LoadButtonText(string path) - { - var result = new Dictionary(); + // private Dictionary LoadButtonText(string path) + // { + // var result = new Dictionary(); - if (!File.Exists(path)) - { - Console.WriteLine($"Warning: text file not found: {path}"); - return result; - } + // if (!File.Exists(path)) + // { + // Console.WriteLine($"Warning: text file not found: {path}"); + // return result; + // } - var lines = File.ReadAllLines(path); - for (int i = 1; i < lines.Length; i++) // skip header row - { - var line = lines[i].Trim(); - if (string.IsNullOrEmpty(line)) continue; + // var lines = File.ReadAllLines(path); + // for (int i = 1; i < lines.Length; i++) // skip header row + // { + // var line = lines[i].Trim(); + // if (string.IsNullOrEmpty(line)) continue; - var parts = line.Split(new[] { ',' }, 2); - if (parts.Length < 2) continue; + // var parts = line.Split(new[] { ',' }, 2); + // if (parts.Length < 2) continue; - result[parts[0]] = parts[1]; - } + // result[parts[0]] = parts[1]; + // } - return result; - } + // return result; + // } // private void ApplyLanguage(bool isEnglish) // { @@ -282,15 +299,15 @@ Console.WriteLine("Exam Mode selected"); } - private void OnLanguageToggled(object sender, EventArgs e) + private void OnLanguageToggled(object sender, EventArgs e) { - ApplyLanguageEverywhere(LanguageToggle.Active); + _Language.Apply(LanguageToggle.Active); } - private void ApplyLanguageEverywhere(bool isEnglish) - { - Language.LanguageSwitchValue = isEnglish; - _Language.ApplyLanguage(isEnglish); - _settingsWindow?._Language?.ApplyLanguages(isEnglish); - } + // private void ApplyLanguageEverywhere(bool isEnglish) + // { + // Language.LanguageSwitchValue = isEnglish; + // _Language.ApplyLanguage(isEnglish); + // _settingsWindow?._Language?.ApplyLanguages(isEnglish); + // } } \ No newline at end of file diff --git a/program/SettingWindow.cs b/program/SettingWindow.cs index 1fed549..f39a427 100644 --- a/program/SettingWindow.cs +++ b/program/SettingWindow.cs @@ -15,9 +15,11 @@ [UI] private Label LogLabel = null; [UI] private TextView LogtextBox = null; - private readonly Dictionary _buttonsById; - private readonly Dictionary _labelsById; - internal Language _Language; + // private readonly Dictionary _buttonsById; + // private readonly Dictionary _labelsById; + // internal Language _Language; + private readonly Language _lang; + private bool _connected; private const int MaxLogLines = 500; @@ -28,14 +30,15 @@ public event EventHandler BackRequested; - public SettingsWindow(Builder builder, ArduinoConnection arduino) + public SettingsWindow(Builder builder, ArduinoConnection arduino, Language language) { builder.Autoconnect(this); _arduino = arduino; + _lang = language; // Title = "Settings"; - ComPortLabel.Text = "COM Port"; - LogLabel.Text = "Log"; + // ComPortLabel.Text = "COM Port"; + // LogLabel.Text = "Log"; LogtextBox.Editable = false; LogtextBox.WrapMode = WrapMode.WordChar; @@ -50,20 +53,29 @@ _arduino.LineReceived += OnLineReceived; _arduino.ConnectionChanged += OnConnectionChanged; - _buttonsById = new Dictionary - { - { "ConnectButton", ConnectButton }, - { "AutoConnectButton", AutoConnectButton }, - { "RefreshButton", RefreshButton }, - { "BackButton", BackButton }, - }; + _lang.Register("ComPortLabel", ComPortLabel); + _lang.Register("LogLabel", LogLabel); + _lang.Register("AutoConnectButton", AutoConnectButton); + _lang.Register("RefreshButton", RefreshButton); + _lang.Register("BackButton", BackButton); + _lang.RegisterDynamic("ConnectButton", ConnectButton); // set in OnConnectionChanged + _lang.RegisterDynamic("StatusLabel", StatusLabel); + _lang.Changed += (s, e) => OnConnectionChanged(_connected); - // CSS ID selectors (#Option1Button etc.) need the widget Name set explicitly - foreach (var kvp in _buttonsById) - kvp.Value.Name = kvp.Key; + // _buttonsById = new Dictionary + // { + // { "ConnectButton", ConnectButton }, + // { "AutoConnectButton", AutoConnectButton }, + // { "RefreshButton", RefreshButton }, + // { "BackButton", BackButton }, + // }; + + // // CSS ID selectors (#Option1Button etc.) need the widget Name set explicitly + // foreach (var kvp in _buttonsById) + // kvp.Value.Name = kvp.Key; - _Language = new Language(_buttonsById); - // _Language.ApplyLanguages(Language.LanguageSwitchValue); + // _Language = new Language(_buttonsById); + // // _Language.ApplyLanguages(Language.LanguageSwitchValue); RefreshPorts(); OnConnectionChanged(_arduino.IsOpen); @@ -82,11 +94,11 @@ if (_ports.Length > 0) { ComPortComboBox.Active = 0; - AppendLog($"{_ports.Length} port(s) found"); + AppendLog(_lang.Format("Log.PortsFound", _ports.Length)); } else { - AppendLog("No serial ports found"); + AppendLog(_lang["Log.NoPorts"]); } } @@ -101,7 +113,7 @@ string port = ComPortComboBox.ActiveText; if (string.IsNullOrEmpty(port)) { - AppendLog("Select a COM port first"); + AppendLog(_lang["Log.SelectPort"]); return; } _arduino.Connect(port); @@ -111,7 +123,7 @@ { AutoConnectButton.Sensitive = false; ConnectButton.Sensitive = false; - AppendLog("Scanning ports for device..."); + AppendLog(_lang["Log.Scanning"]); string found = await _arduino.DetectPortAsync(); @@ -123,7 +135,7 @@ } else { - AppendLog("Device not found — use manual connection"); + AppendLog(_lang["Log.DeviceNotFound"]); } AutoConnectButton.Sensitive = true; @@ -152,10 +164,15 @@ private void OnConnectionChanged(bool connected) { - ConnectButton.Label = connected ? "Disconnect" : "Connect"; + _connected = connected; + + ConnectButton.Label = connected + ? _lang["ConnectButton.Disconnect"] + : _lang["ConnectButton.Connect"]; + StatusLabel.Text = connected - ? $"Connected ({_arduino.PortName})" - : "Disconnected"; + ? _lang.Format("StatusLabel.Connected", _arduino.PortName) + : _lang["StatusLabel.Disconnected"]; ComPortComboBox.Sensitive = !connected; RefreshButton.Sensitive = !connected; @@ -171,8 +188,8 @@ if (line.StartsWith("Send Status:")) return; if (line.StartsWith("\u2713") || line.StartsWith("\u2717")) return; - if (line.Contains("ESPNOW_CONNECTED")) { AppendLog("[ESP-NOW] Peer connected"); return; } - if (line.Contains("ESPNOW_DISCONNECTED")) { AppendLog("[ESP-NOW] Peer disconnected"); return; } + if (line.Contains("ESPNOW_CONNECTED")) { AppendLog(_lang["Log.EspNowConnected"]); return; } + if (line.Contains("ESPNOW_DISCONNECTED")) { AppendLog(_lang["Log.EspNowDisconnected"]); return; } AppendLog(line); } diff --git a/program/info_eng.csv b/program/info_eng.csv index 3a0481a..a7bb3e8 100644 --- a/program/info_eng.csv +++ b/program/info_eng.csv @@ -1,11 +1,26 @@ -ButtonId,Text +key,text Option1Button,Normal Session Option2Button,Position Recording Mode Option3Button,Exam Mode -CloseButton,Close SettingButton,Settings -ConnectButton,Connect -AutoConnectButton,Auto Connect -RefreshButton, Refresh Port +CloseButton,Close +LanguageLabel,Language +LanguageToggle,あ +ComPortLabel,COM Port LogLabel,Log +AutoConnectButton,Auto Connect +RefreshButton,Refresh Ports BackButton,Back +ConnectButton.Connect,Connect +ConnectButton.Disconnect,Disconnect +StatusLabel.Connected,Connected ({0}) +StatusLabel.Disconnected,Disconnected +ConnectionIcon.Connected,Connected to {0} +ConnectionIcon.Disconnected,Device not connected +Log.PortsFound,{0} port(s) found +Log.NoPorts,No serial ports found +Log.SelectPort,Select a COM port first +Log.Scanning,Scanning ports for device... +Log.DeviceNotFound,Device not found — use manual connection +Log.EspNowConnected,[ESP-NOW] Peer connected +Log.EspNowDisconnected,[ESP-NOW] Peer disconnected \ No newline at end of file diff --git a/program/info_jpn.csv b/program/info_jpn.csv index 1d4e6e8..5cac763 100644 --- a/program/info_jpn.csv +++ b/program/info_jpn.csv @@ -1,11 +1,26 @@ -ButtonId,Text +key,text Option1Button,通常セッション Option2Button,位置記録モード Option3Button,試験モード -CloseButton,閉じる SettingButton,設定 -ConnectButton,接続 +CloseButton,終了 +LanguageLabel,言語 +LanguageToggle,A +ComPortLabel,COMポート +LogLabel,ログ AutoConnectButton,自動接続 -RefreshButton, ポートの更新 -LogLabel,情報 +RefreshButton,ポート更新 BackButton,戻る +ConnectButton.Connect,接続 +ConnectButton.Disconnect,切断 +StatusLabel.Connected,接続済み ({0}) +StatusLabel.Disconnected,未接続 +ConnectionIcon.Connected,{0} に接続しました +ConnectionIcon.Disconnected,デバイスが接続されていません +Log.PortsFound,ポートが {0} 個見つかりました +Log.NoPorts,シリアルポートが見つかりません +Log.SelectPort,COMポートを選択してください +Log.Scanning,デバイスを検索中... +Log.DeviceNotFound,デバイスが見つかりません — 手動で接続してください +Log.EspNowConnected,[ESP-NOW] 相手が接続しました +Log.EspNowDisconnected,[ESP-NOW] 相手が切断しました \ No newline at end of file