diff --git a/BLE_tcp_bridge/AppConfig.cs b/BLE_tcp_bridge/AppConfig.cs
index 86affe32..e199d8fd 100644
--- a/BLE_tcp_bridge/AppConfig.cs
+++ b/BLE_tcp_bridge/AppConfig.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using System.Text;
@@ -39,7 +39,7 @@ public static AppConfig Load()
}
catch (Exception ex)
{
- Console.WriteLine("配置文件读取失败: " + ex.Message);
+ Console.WriteLine(BridgeText.T("configReadError", ex.Message));
return CreateDefault();
}
}
@@ -60,7 +60,7 @@ public void Save()
}
catch (Exception ex)
{
- Console.WriteLine("配置文件保存失败: " + ex.Message);
+ Console.WriteLine(BridgeText.T("configWriteError", ex.Message));
}
}
diff --git a/BLE_tcp_bridge/BLE_tcp_driver.csproj b/BLE_tcp_bridge/BLE_tcp_driver.csproj
index dc6da56b..af96d9cf 100644
--- a/BLE_tcp_bridge/BLE_tcp_driver.csproj
+++ b/BLE_tcp_bridge/BLE_tcp_driver.csproj
@@ -68,6 +68,7 @@
Form1.cs
+
@@ -75,6 +76,15 @@
Form1.cs
+
+ BLE_tcp_driver.Messages.en.resources
+
+
+ BLE_tcp_driver.Messages.ru.resources
+
+
+ BLE_tcp_driver.Messages.zh.resources
+
ResXFileCodeGenerator
Resources.Designer.cs
@@ -115,4 +125,4 @@
-
\ No newline at end of file
+
diff --git a/BLE_tcp_bridge/BridgeText.cs b/BLE_tcp_bridge/BridgeText.cs
new file mode 100644
index 00000000..77e9825a
--- /dev/null
+++ b/BLE_tcp_bridge/BridgeText.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Resources;
+
+namespace BLE_tcp_driver
+{
+ /// UI text only; language never changes wire data or diagnostic IDs.
+ internal static class BridgeText
+ {
+ private static readonly ResourceManager English = Manager("en");
+ private static ResourceManager selected = English;
+ internal static string Language { get; private set; } = "en";
+
+ private static ResourceManager Manager(string language) =>
+ new ResourceManager("BLE_tcp_driver.Messages." + language, typeof(BridgeText).Assembly);
+
+ internal static string Normalize(string language)
+ {
+ string code = (language ?? "").Trim().ToLowerInvariant().Split('-', '_')[0];
+ return code == "ru" || code == "zh" ? code : "en";
+ }
+
+ internal static void Initialize(string[] arguments, string preferencesPath, string systemLanguage)
+ {
+ string explicitLanguage = arguments.FirstOrDefault(arg =>
+ arg.StartsWith("--language=", StringComparison.OrdinalIgnoreCase));
+ string language = explicitLanguage == null ? null : explicitLanguage.Substring(11);
+ if (string.IsNullOrWhiteSpace(language))
+ {
+ try
+ {
+ if (File.Exists(preferencesPath))
+ foreach (string line in File.ReadLines(preferencesPath))
+ {
+ string entry = line.Trim();
+ if (entry.StartsWith("#") || entry.StartsWith("!")) continue;
+ int delimiter = entry.IndexOfAny(new[] { '=', ':' });
+ if (delimiter > 0 && entry.Substring(0, delimiter).Trim() == "AhaKeySelectedLanguage")
+ language = entry.Substring(delimiter + 1).Trim();
+ }
+ }
+ catch (IOException) { }
+ catch (UnauthorizedAccessException) { }
+ }
+ Language = Normalize(string.IsNullOrWhiteSpace(language) ? systemLanguage : language);
+ selected = Manager(Language);
+ }
+
+ internal static string T(string key, params object[] arguments)
+ {
+ string template = selected.GetString(key, CultureInfo.InvariantCulture)
+ ?? English.GetString(key, CultureInfo.InvariantCulture);
+ if (template == null) throw new InvalidOperationException("Missing bridge translation: " + key);
+ return arguments.Length == 0 ? template
+ : string.Format(CultureInfo.GetCultureInfo(Language), template, arguments);
+ }
+ }
+}
diff --git a/BLE_tcp_bridge/Form1.Designer.cs b/BLE_tcp_bridge/Form1.Designer.cs
index a53ffda9..40335415 100644
--- a/BLE_tcp_bridge/Form1.Designer.cs
+++ b/BLE_tcp_bridge/Form1.Designer.cs
@@ -59,13 +59,13 @@ private void InitializeComponent()
this.BtnConnect.Name = "BtnConnect";
this.BtnConnect.Size = new System.Drawing.Size(75, 23);
this.BtnConnect.TabIndex = 5;
- this.BtnConnect.Text = "连接";
+ this.BtnConnect.Text = BridgeText.T("connect");
this.BtnConnect.UseVisualStyleBackColor = true;
this.BtnConnect.Click += new System.EventHandler(this.BtnConnect_Click);
//
// rtbMsg
//
- this.rtbMsg.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+ this.rtbMsg.Font = new System.Drawing.Font("Consolas", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.rtbMsg.Location = new System.Drawing.Point(0, 116);
this.rtbMsg.Name = "rtbMsg";
this.rtbMsg.ReadOnly = true;
@@ -83,7 +83,7 @@ private void InitializeComponent()
this.label_connected_devices.Name = "label_connected_devices";
this.label_connected_devices.Size = new System.Drawing.Size(95, 12);
this.label_connected_devices.TabIndex = 7;
- this.label_connected_devices.Text = "当前连接设备:无";
+ this.label_connected_devices.Text = BridgeText.T("connectedDevice", BridgeText.T("none"));
//
// label_ip_port
//
@@ -92,7 +92,7 @@ private void InitializeComponent()
this.label_ip_port.Name = "label_ip_port";
this.label_ip_port.Size = new System.Drawing.Size(131, 12);
this.label_ip_port.TabIndex = 8;
- this.label_ip_port.Text = "当前服务器地址及端口:";
+ this.label_ip_port.Text = BridgeText.T("server", "—", 0, 0);
//
// checkBox_start_mode
//
@@ -101,14 +101,14 @@ private void InitializeComponent()
this.checkBox_start_mode.Name = "checkBox_start_mode";
this.checkBox_start_mode.Size = new System.Drawing.Size(108, 16);
this.checkBox_start_mode.TabIndex = 9;
- this.checkBox_start_mode.Text = "下次最小化启动";
+ this.checkBox_start_mode.Text = BridgeText.T("startMinimized");
this.checkBox_start_mode.UseVisualStyleBackColor = true;
//
// notifyIcon1
//
this.notifyIcon1.ContextMenuStrip = this.trayContextMenu;
this.notifyIcon1.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon1.Icon")));
- this.notifyIcon1.Text = "BLE TCP Bridge";
+ this.notifyIcon1.Text = BridgeText.T("tray");
this.notifyIcon1.Visible = true;
this.notifyIcon1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.notifyIcon1_MouseClick);
//
@@ -123,7 +123,7 @@ private void InitializeComponent()
//
this.tsmiExit.Name = "tsmiExit";
this.tsmiExit.Size = new System.Drawing.Size(124, 22);
- this.tsmiExit.Text = "退出程序";
+ this.tsmiExit.Text = BridgeText.T("exit");
this.tsmiExit.Click += new System.EventHandler(this.tsmiExit_Click);
//
// button1
@@ -132,7 +132,7 @@ private void InitializeComponent()
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 10;
- this.button1.Text = "退出程序";
+ this.button1.Text = BridgeText.T("exit");
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
@@ -143,7 +143,7 @@ private void InitializeComponent()
this.checkBox_follow_system.Name = "checkBox_follow_system";
this.checkBox_follow_system.Size = new System.Drawing.Size(96, 16);
this.checkBox_follow_system.TabIndex = 11;
- this.checkBox_follow_system.Text = "跟随系统启动";
+ this.checkBox_follow_system.Text = BridgeText.T("startWithWindows");
this.checkBox_follow_system.UseVisualStyleBackColor = true;
this.checkBox_follow_system.CheckedChanged += new System.EventHandler(this.checkBox_follow_system_CheckedChanged);
//
diff --git a/BLE_tcp_bridge/Form1.cs b/BLE_tcp_bridge/Form1.cs
index c3b6d05c..8db0855f 100644
--- a/BLE_tcp_bridge/Form1.cs
+++ b/BLE_tcp_bridge/Form1.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
@@ -26,6 +26,7 @@ public partial class Form1 : Form
public Form1()
{
InitializeComponent();
+ ConfigureLocalizedLayout();
// Stable title used by Studio to restore/adopt this exact bridge window.
Text = "AhaKey BLE TCP Driver";
config = AppConfig.Load();
@@ -50,6 +51,32 @@ protected override void SetVisibleCore(bool value)
base.SetVisibleCore(value);
}
+ private void ConfigureLocalizedLayout()
+ {
+ // Flow layout accommodates translated text and Windows font scaling.
+ Font = new Font("Segoe UI", 9F);
+ MinimumSize = new Size(680, 420);
+ var header = new TableLayoutPanel
+ {
+ Dock = DockStyle.Top, AutoSize = true, ColumnCount = 1,
+ Padding = new Padding(12)
+ };
+ var connection = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill };
+ DeviceSelect.Width = 240;
+ BtnConnect.AutoSize = true;
+ button1.AutoSize = true;
+ connection.Controls.AddRange(new Control[] { DeviceSelect, BtnConnect, button1 });
+ var options = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill };
+ options.Controls.AddRange(new Control[] { checkBox_start_mode, checkBox_follow_system });
+ header.Controls.Add(connection);
+ header.Controls.Add(label_connected_devices);
+ header.Controls.Add(label_ip_port);
+ header.Controls.Add(options);
+ rtbMsg.Dock = DockStyle.Fill;
+ rtbMsg.Font = new Font("Consolas", 10F);
+ Controls.Add(header);
+ }
+
private void log(Color c, string message)
{
BeginInvoke(new Action(() =>
@@ -68,7 +95,7 @@ private void Form1_Load(object sender, EventArgs e)
if (_loaded) return;
_loaded = true;
- this.Opacity = 0.8;
+ this.Opacity = 1.0;
// 同步checkbox
checkBox_start_mode.Checked = config.StartMinimized;
@@ -97,11 +124,11 @@ private void Form1_Load(object sender, EventArgs e)
{
BeginInvoke(new Action(() =>
{
- label_ip_port.Text = $"TCP服务: {TcpServer.GetLocalIPAddress()}:{config.ServerPort} (客户端:{count})";
+ label_ip_port.Text = BridgeText.T("server", TcpServer.GetLocalIPAddress(), config.ServerPort, count);
}));
};
tcpServer.Start();
- label_ip_port.Text = $"TCP服务: {TcpServer.GetLocalIPAddress()}:{config.ServerPort} (客户端:0)";
+ label_ip_port.Text = BridgeText.T("server", TcpServer.GetLocalIPAddress(), config.ServerPort, 0);
// 重试定时器 (UI线程Timer)
retryTimer = new Timer();
@@ -111,11 +138,11 @@ private void Form1_Load(object sender, EventArgs e)
// 自动开始扫描
devicesList = new List();
bleCore.StartBleDeviceWatcher();
- log(Color.Blue, "自动扫描蓝牙设备中...");
+ log(Color.Blue, BridgeText.T("scan"));
if (config.HasSavedDevice)
{
- log(Color.Blue, $"目标设备: {config.BleName} [{config.BleMac}]");
+ log(Color.Blue, BridgeText.T("target", config.BleName, config.BleMac));
retryTimer.Start();
}
}
@@ -133,13 +160,13 @@ private void RetryTimer_Tick(object sender, EventArgs e)
try { bleCore.StopBleDeviceWatcher(); } catch { }
bleCore.StartBleDeviceWatcher();
- log(Color.Gray, "重新扫描蓝牙设备...");
+ log(Color.Gray, BridgeText.T("rescan"));
}
private void WriteDataSuccess(GattCharacteristic sender, byte[] data)
{
UTF8Encoding utf8 = new UTF8Encoding();
- log(Color.FromArgb(0x00ff00FF), Utilities.ConvertUuidToShortId(sender.Uuid).ToString() + "write :" + utf8.GetString(data));
+ log(Color.FromArgb(0x00ff00FF), BridgeText.T("write", Utilities.ConvertUuidToShortId(sender.Uuid), utf8.GetString(data)));
}
private void ReceiveNotifyData(GattCharacteristic sender, byte[] data)
@@ -147,14 +174,11 @@ private void ReceiveNotifyData(GattCharacteristic sender, byte[] data)
if (ProtocolHelper.IsDeviceStatusNotification(data))
{
var info = ProtocolHelper.ParseDeviceStatusFromNotification(data);
- log(Color.DarkGreen, $"设备状态: 电量={info.BatteryLevel} 信号={info.SignalStrength} " +
- $"固件={info.FirmwareVersionMain}.{info.FirmwareVersionSub} " +
- $"模式={info.WorkMode} 灯光={info.LightMode} 开关={info.SwitchState}");
+ log(Color.DarkGreen, BridgeText.T("status", info.BatteryLevel, info.SignalStrength, info.FirmwareVersionMain, info.FirmwareVersionSub, info.WorkMode, info.LightMode, info.SwitchState));
}
else
{
- log(Color.FromArgb(0x00ff0000), Utilities.ConvertUuidToShortId(sender.Uuid).ToString() +
- "receive :" + BitConverter.ToString(data));
+ log(Color.FromArgb(0x00ff0000), BridgeText.T("receive", Utilities.ConvertUuidToShortId(sender.Uuid), BitConverter.ToString(data)));
}
}
@@ -177,7 +201,7 @@ private void DeviceAdded(DeviceInformation deviceInformation)
string.Equals(mac, config.BleMac, StringComparison.OrdinalIgnoreCase))
{
autoConnecting = true;
- log(Color.Blue, "发现目标设备, 自动连接...");
+ log(Color.Blue, BridgeText.T("targetConnecting"));
try { bleCore.StopBleDeviceWatcher(); } catch { }
bleCore.ConnectDeviceByInfo(deviceInformation);
}
@@ -189,8 +213,8 @@ private void DeviceConnected(BluetoothLEDevice bluetoothLEDevice)
{
BeginInvoke(new Action(() =>
{
- log(Color.FromArgb(0x00ff00FF), "Connected:" + bluetoothLEDevice.Name);
- label_connected_devices.Text = "当前连接设备:" + bluetoothLEDevice.Name;
+ log(Color.FromArgb(0x00ff00FF), BridgeText.T("connected", bluetoothLEDevice.Name));
+ label_connected_devices.Text = BridgeText.T("connectedDevice", bluetoothLEDevice.Name);
retryTimer.Stop();
autoConnecting = false;
targetConfirmed = false;
@@ -201,13 +225,13 @@ private void DeviceDisconnected(BluetoothLEDevice bluetoothLEDevice)
{
BeginInvoke(new Action(() =>
{
- log(Color.Red, "Disconnected:" + (bluetoothLEDevice?.Name ?? ""));
- label_connected_devices.Text = "当前连接设备:无";
+ log(Color.Red, BridgeText.T("disconnected", bluetoothLEDevice?.Name ?? ""));
+ label_connected_devices.Text = BridgeText.T("connectedDevice", BridgeText.T("none"));
autoConnecting = false;
if (config.HasSavedDevice)
{
- log(Color.Gray, "将在数秒后尝试重连...");
+ log(Color.Gray, BridgeText.T("retry"));
retryTimer.Start();
}
}));
@@ -218,8 +242,7 @@ private void CharacteristicAdded(GattCharacteristic gattCharacteristic)
BeginInvoke(new Action(() =>
{
ushort shortId = Utilities.ConvertUuidToShortId(gattCharacteristic.Uuid);
- log(Color.Black, "Chara:0x" + shortId.ToString("X") +
- ", des:" + gattCharacteristic.UserDescription);
+ log(Color.Black, BridgeText.T("characteristic", shortId, gattCharacteristic.UserDescription));
}));
}
@@ -238,21 +261,21 @@ private void OnAllCharacteristicsDiscovered()
if (!allFound)
{
- string devName = bleCore.CurrentDevice?.Name ?? "未知";
- log(Color.OrangeRed, $"设备 [{devName}] 未找齐目标UUID, 断开连接");
+ string devName = bleCore.CurrentDevice?.Name ?? BridgeText.T("unknown");
+ log(Color.OrangeRed, BridgeText.T("missingCharacteristics", devName));
bleCore.Dispose();
- label_connected_devices.Text = "当前连接设备:无";
+ label_connected_devices.Text = BridgeText.T("connectedDevice", BridgeText.T("none"));
if (config.HasSavedDevice)
{
- log(Color.Gray, "将继续尝试查找目标设备...");
+ log(Color.Gray, BridgeText.T("continueSearch"));
retryTimer.Start();
}
}
else
{
targetConfirmed = true;
- log(Color.Blue, "目标设备已确认, 所有特征就绪");
+ log(Color.Blue, BridgeText.T("ready"));
SaveCurrentDeviceToConfig();
}
}));
@@ -272,7 +295,7 @@ private void SaveCurrentDeviceToConfig()
config.BleName = bleCore.CurrentDevice.Name;
config.BleMac = mac;
config.Save();
- log(Color.Blue, $"已保存设备: {config.BleName} [{config.BleMac}]");
+ log(Color.Blue, BridgeText.T("saved", config.BleName, config.BleMac));
}
private void BtnConnect_Click(object sender, EventArgs e)
@@ -315,13 +338,13 @@ private void BindRichTextBoxContextMenu(RichTextBox textBox)
{
ContextMenu contextMenu = new ContextMenu();
- System.Windows.Forms.MenuItem cutItem = new System.Windows.Forms.MenuItem("剪切");
+ System.Windows.Forms.MenuItem cutItem = new System.Windows.Forms.MenuItem(BridgeText.T("cut"));
cutItem.Click += (sender, eventArgs) => textBox.Cut();
- System.Windows.Forms.MenuItem copyItem = new System.Windows.Forms.MenuItem("复制");
+ System.Windows.Forms.MenuItem copyItem = new System.Windows.Forms.MenuItem(BridgeText.T("copy"));
copyItem.Click += (sender, eventArgs) => textBox.Copy();
- System.Windows.Forms.MenuItem pasteItem = new System.Windows.Forms.MenuItem("粘贴");
+ System.Windows.Forms.MenuItem pasteItem = new System.Windows.Forms.MenuItem(BridgeText.T("paste"));
pasteItem.Click += (sender, eventArgs) => textBox.Paste();
contextMenu.MenuItems.Add(cutItem);
@@ -396,7 +419,7 @@ private void checkBox_follow_system_CheckedChanged(object sender, EventArgs e)
}
catch (Exception ex)
{
- MessageBox.Show("设置开机自启动失败: " + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ MessageBox.Show(BridgeText.T("autoStartError", ex.Message), BridgeText.T("error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
// 还原 checkbox 状态
checkBox_follow_system.CheckedChanged -= checkBox_follow_system_CheckedChanged;
checkBox_follow_system.Checked = !checkBox_follow_system.Checked;
diff --git a/BLE_tcp_bridge/Localization/Messages_en.resx b/BLE_tcp_bridge/Localization/Messages_en.resx
new file mode 100644
index 00000000..0efeeffd
--- /dev/null
+++ b/BLE_tcp_bridge/Localization/Messages_en.resx
@@ -0,0 +1,156 @@
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+
+
+ Connect
+
+
+ Connected device: {0}
+
+
+ None
+
+
+ Unknown
+
+
+ TCP server: {0}:{1} (clients: {2})
+
+
+ Start minimized next time
+
+
+ Start with Windows
+
+
+ AhaKey BLE bridge
+
+
+ Exit
+
+
+ Scanning for Bluetooth devices...
+
+
+ Scanning for Bluetooth devices again...
+
+
+ Target device: {0} [{1}]
+
+
+ Target found; connecting automatically...
+
+
+ Connected: {0}
+
+
+ Disconnected: {0}
+
+
+ Will retry the connection in a few seconds...
+
+
+ Characteristic 0x{0:X}: {1}
+
+
+ Device [{0}] lacks required characteristics; disconnecting
+
+
+ Continuing to search for the target device...
+
+
+ Target confirmed; all characteristics are ready
+
+
+ Device saved: {0} [{1}]
+
+
+ Device status: battery={0}, signal={1}, firmware={2}.{3}, mode={4}, light={5}, switch={6}
+
+
+ 0x{0:X} write: {1}
+
+
+ 0x{0:X} received: {1}
+
+
+ Cut
+
+
+ Copy
+
+
+ Paste
+
+
+ Error
+
+
+ Could not change Windows startup settings: {0}
+
+
+ TCP server started on port {0}
+
+
+ TCP server stopped
+
+
+ TCP client connected: {0}
+
+
+ Connection accept error: {0}
+
+
+ Client processing error: {0}
+
+
+ BLE data (0x7341): {0} bytes
+
+
+ BLE data characteristic (0x7341) is not ready
+
+
+ BLE command (0x7343): {0} bytes
+
+
+ BLE command characteristic (0x7343) is not ready
+
+
+ Responding to BLE connection status query
+
+
+ Responding to cached device information query
+
+
+ Unknown packet type: 0x{0:X2}
+
+
+ TCP client disconnected: {0}
+
+
+ Could not read configuration: {0}
+
+
+ Could not save configuration: {0}
+
+
+ Target service and characteristics:
+
+
+ YES
+
+
+ NO
+
+
\ No newline at end of file
diff --git a/BLE_tcp_bridge/Localization/Messages_ru.resx b/BLE_tcp_bridge/Localization/Messages_ru.resx
new file mode 100644
index 00000000..c234ada5
--- /dev/null
+++ b/BLE_tcp_bridge/Localization/Messages_ru.resx
@@ -0,0 +1,156 @@
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+
+
+ Подключить
+
+
+ Подключённое устройство: {0}
+
+
+ Нет
+
+
+ Неизвестно
+
+
+ TCP-сервер: {0}:{1} (клиентов: {2})
+
+
+ Запускать свёрнутым
+
+
+ Запускать вместе с Windows
+
+
+ BLE-мост AhaKey
+
+
+ Выход
+
+
+ Поиск устройств Bluetooth…
+
+
+ Повторный поиск устройств Bluetooth…
+
+
+ Целевое устройство: {0} [{1}]
+
+
+ Устройство найдено; автоматическое подключение…
+
+
+ Подключено: {0}
+
+
+ Отключено: {0}
+
+
+ Повторное подключение через несколько секунд…
+
+
+ Характеристика 0x{0:X}: {1}
+
+
+ У устройства [{0}] не найдены нужные характеристики; отключение
+
+
+ Поиск целевого устройства продолжается…
+
+
+ Устройство подтверждено; все характеристики готовы
+
+
+ Устройство сохранено: {0} [{1}]
+
+
+ Состояние: заряд={0}, сигнал={1}, прошивка={2}.{3}, режим={4}, подсветка={5}, переключатель={6}
+
+
+ 0x{0:X} отправлено: {1}
+
+
+ 0x{0:X} получено: {1}
+
+
+ Вырезать
+
+
+ Копировать
+
+
+ Вставить
+
+
+ Ошибка
+
+
+ Не удалось изменить автозапуск: {0}
+
+
+ TCP-сервер запущен на порту {0}
+
+
+ TCP-сервер остановлен
+
+
+ TCP-клиент подключён: {0}
+
+
+ Ошибка приёма соединения: {0}
+
+
+ Ошибка обработки клиента: {0}
+
+
+ Данные BLE (0x7341): {0} байт
+
+
+ Характеристика данных BLE (0x7341) не готова
+
+
+ Команда BLE (0x7343): {0} байт
+
+
+ Характеристика команд BLE (0x7343) не готова
+
+
+ Ответ на запрос состояния соединения BLE
+
+
+ Ответ на запрос сохранённых сведений об устройстве
+
+
+ Неизвестный тип пакета: 0x{0:X2}
+
+
+ TCP-клиент отключён: {0}
+
+
+ Не удалось прочитать настройки: {0}
+
+
+ Не удалось сохранить настройки: {0}
+
+
+ Целевой сервис и характеристики:
+
+
+ ДА
+
+
+ НЕТ
+
+
\ No newline at end of file
diff --git a/BLE_tcp_bridge/Localization/Messages_zh.resx b/BLE_tcp_bridge/Localization/Messages_zh.resx
new file mode 100644
index 00000000..ec2f48bb
--- /dev/null
+++ b/BLE_tcp_bridge/Localization/Messages_zh.resx
@@ -0,0 +1,156 @@
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+
+
+ 连接
+
+
+ 当前连接设备: {0}
+
+
+ 无
+
+
+ 未知
+
+
+ TCP服务: {0}:{1} (客户端: {2})
+
+
+ 下次最小化启动
+
+
+ 跟随系统启动
+
+
+ AhaKey BLE 桥接
+
+
+ 退出程序
+
+
+ 自动扫描蓝牙设备中...
+
+
+ 重新扫描蓝牙设备...
+
+
+ 目标设备: {0} [{1}]
+
+
+ 发现目标设备, 自动连接...
+
+
+ 已连接: {0}
+
+
+ 已断开: {0}
+
+
+ 将在数秒后尝试重连...
+
+
+ 特征 0x{0:X}: {1}
+
+
+ 设备 [{0}] 未找齐目标UUID, 断开连接
+
+
+ 将继续尝试查找目标设备...
+
+
+ 目标设备已确认, 所有特征就绪
+
+
+ 已保存设备: {0} [{1}]
+
+
+ 设备状态: 电量={0}, 信号={1}, 固件={2}.{3}, 模式={4}, 灯光={5}, 开关={6}
+
+
+ 0x{0:X} 写入: {1}
+
+
+ 0x{0:X} 收到: {1}
+
+
+ 剪切
+
+
+ 复制
+
+
+ 粘贴
+
+
+ 错误
+
+
+ 设置开机自启动失败: {0}
+
+
+ TCP服务器已启动, 监听端口: {0}
+
+
+ TCP服务器已停止
+
+
+ TCP客户端已连接: {0}
+
+
+ 接受连接异常: {0}
+
+
+ 客户端处理异常: {0}
+
+
+ BLE数据(0x7341): {0}字节
+
+
+ BLE数据特征(0x7341)未就绪
+
+
+ BLE命令(0x7343): {0}字节
+
+
+ BLE命令特征(0x7343)未就绪
+
+
+ 响应BLE状态查询
+
+
+ 响应设备信息查询
+
+
+ 未知包类型: 0x{0:X2}
+
+
+ TCP客户端已断开: {0}
+
+
+ 配置文件读取失败: {0}
+
+
+ 配置文件保存失败: {0}
+
+
+ 目标服务和特征:
+
+
+ 是
+
+
+ 否
+
+
\ No newline at end of file
diff --git a/BLE_tcp_bridge/Program.cs b/BLE_tcp_bridge/Program.cs
index eb6863a8..2af35b78 100644
--- a/BLE_tcp_bridge/Program.cs
+++ b/BLE_tcp_bridge/Program.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -247,11 +247,11 @@ private void FinishDiscoveryIfReady(int generation)
if (!shouldNotify) return;
- LogDiscovery("TARGET SUMMARY:");
- LogDiscovery("7340 service = " + (serviceFound ? "YES" : "NO"));
- LogDiscovery("7341 = " + (dataFound ? "YES" : "NO"));
- LogDiscovery("7343 = " + (writeFound ? "YES" : "NO"));
- LogDiscovery("7344 = " + (notifyFound ? "YES" : "NO"));
+ LogDiscovery(BridgeText.T("discoverySummary"));
+ LogDiscovery("7340 service = " + BridgeText.T(serviceFound ? "yes" : "no"));
+ LogDiscovery("7341 = " + BridgeText.T(dataFound ? "yes" : "no"));
+ LogDiscovery("7343 = " + BridgeText.T(writeFound ? "yes" : "no"));
+ LogDiscovery("7344 = " + BridgeText.T(notifyFound ? "yes" : "no"));
// 额外输出机器可读的诊断结果,便于从 UI 日志直接复制给 HV-005 调查。
LogDiscovery("SERVICE_7340_FOUND=" + (serviceFound ? "YES" : "NO"));
LogDiscovery("CHAR_7341_FOUND=" + (dataFound ? "YES" : "NO"));
@@ -993,6 +993,10 @@ private static void ActivateExistingWindow()
[STAThread]
static void Main()
{
+ BridgeText.Initialize(Environment.GetCommandLineArgs(),
+ System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ ".ahakey", "preferences.properties"),
+ System.Globalization.CultureInfo.CurrentUICulture.Name);
bool created;
singleInstanceMutex = new Mutex(true, "Global\\AhaKey.BLETcpDriver", out created);
if (!created)
diff --git a/BLE_tcp_bridge/TcpServer.cs b/BLE_tcp_bridge/TcpServer.cs
index 4180cc77..18e783c2 100644
--- a/BLE_tcp_bridge/TcpServer.cs
+++ b/BLE_tcp_bridge/TcpServer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -51,7 +51,7 @@ public void Start()
// 订阅BLE通知, 转发给所有TCP客户端
_bleCore.ReceiveNotifyData += OnBleNotify;
- Log($"TCP服务器已启动, 监听端口: {_port}");
+ Log(BridgeText.T("tcpStarted", _port));
Task.Run(() => AcceptLoop());
}
@@ -70,7 +70,7 @@ public void Stop()
_clients.Clear();
}
OnClientCountChanged?.Invoke(0);
- Log("TCP服务器已停止");
+ Log(BridgeText.T("tcpStopped"));
}
///
@@ -86,7 +86,7 @@ private async Task AcceptLoop()
lock (_clientLock) _clients.Add(client);
string ep = GetEndpointString(client);
- Log($"TCP客户端已连接: {ep}");
+ Log(BridgeText.T("clientConnected", ep));
OnClientCountChanged?.Invoke(ClientCount);
var _ = Task.Run(() => ClientLoop(client));
@@ -95,7 +95,7 @@ private async Task AcceptLoop()
catch (SocketException) { if (!_running) break; }
catch (Exception ex)
{
- if (_running) Log($"接受连接异常: {ex.Message}");
+ if (_running) Log(BridgeText.T("acceptError", ex.Message));
}
}
}
@@ -138,7 +138,7 @@ private async Task ClientLoop(TcpClient client)
catch (IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
- catch (Exception ex) { Log($"客户端处理异常: {ex.Message}"); }
+ catch (Exception ex) { Log(BridgeText.T("clientError", ex.Message)); }
finally
{
RemoveClient(client);
@@ -160,10 +160,10 @@ private void HandlePacket(TcpClient client, PacketType type, byte[] data)
_bleCore.WriteDataToCharacterstuc(_bleCore.CurrentDataCharacteristic, data.Skip(i).Take(Math.Min(data.Count() - i, 200)).ToArray());
}
//_bleCore.WriteDataToCharacterstuc(_bleCore.CurrentDataCharacteristic, data ?? new byte[0]);
- Log($"→BLE数据(0x7341) [{data?.Length ?? 0}字节]");
+ Log(BridgeText.T("bleData", data?.Length ?? 0));
}
else
- Log("BLE数据特征(0x7341)未就绪");
+ Log(BridgeText.T("bleDataNotReady"));
break;
case PacketType.WriteCommand:
@@ -179,27 +179,27 @@ private void HandlePacket(TcpClient client, PacketType type, byte[] data)
_bleCore.WriteDataToCharacterstuc(_bleCore.CurrentWriteCharacteristic, data.Skip(i).Take(Math.Min(data.Count() - i, 20)).ToArray());
}
//_bleCore.WriteDataToCharacterstuc(_bleCore.CurrentWriteCharacteristic, data ?? new byte[0]);
- Log($"→BLE命令(0x7343) [{data?.Length ?? 0}字节]");
+ Log(BridgeText.T("bleCommand", data?.Length ?? 0));
}
else
- Log("BLE命令特征(0x7343)未就绪");
+ Log(BridgeText.T("bleCommandNotReady"));
break;
case PacketType.QueryBleStatus:
var status = BuildBleStatus();
SendToClient(client, ProtocolHelper.BuildBleStatusPacket(status));
- Log("响应BLE状态查询");
+ Log(BridgeText.T("statusQuery"));
break;
case PacketType.QueryDeviceInfo:
DeviceStatusInfo info;
lock (_statusLock) info = _deviceStatus;
SendToClient(client, ProtocolHelper.BuildDeviceInfoPacket(info));
- Log("响应设备信息查询");
+ Log(BridgeText.T("infoQuery"));
break;
default:
- Log($"未知包类型: 0x{(byte)type:X2}");
+ Log(BridgeText.T("unknownPacket", (byte)type));
break;
}
}
@@ -247,9 +247,7 @@ private void OnBleNotify(GattCharacteristic sender, byte[] data)
{
var newStatus = ProtocolHelper.ParseDeviceStatusFromNotification(data);
lock (_statusLock) _deviceStatus = newStatus;
- Log($"设备状态更新: 电量={newStatus.BatteryLevel} 信号={newStatus.SignalStrength} " +
- $"固件={newStatus.FirmwareVersionMain}.{newStatus.FirmwareVersionSub} " +
- $"工作模式={newStatus.WorkMode} 灯光={newStatus.LightMode} 开关={newStatus.SwitchState}");
+ Log(BridgeText.T("status", newStatus.BatteryLevel, newStatus.SignalStrength, newStatus.FirmwareVersionMain, newStatus.FirmwareVersionSub, newStatus.WorkMode, newStatus.LightMode, newStatus.SwitchState));
}
byte[] packet = ProtocolHelper.BuildPacket(PacketType.BleNotify, data);
@@ -289,7 +287,7 @@ private void RemoveClient(TcpClient client)
if (removed)
{
string ep = GetEndpointString(client);
- Log($"TCP客户端已断开: {ep}");
+ Log(BridgeText.T("clientDisconnected", ep));
try { client.Close(); } catch { }
OnClientCountChanged?.Invoke(ClientCount);
}
diff --git a/BLE_tcp_bridge/readme.md b/BLE_tcp_bridge/readme.md
index c5c71037..45c9b83a 100644
--- a/BLE_tcp_bridge/readme.md
+++ b/BLE_tcp_bridge/readme.md
@@ -1,3 +1,21 @@
### 使用vs .NetFramework开发的 BLE - TCP 桥接
-给python写的vibe code 上位机和设备间通信用的
\ No newline at end of file
+给python写的vibe code 上位机和设备间通信用的
+
+### Interface languages / Языки интерфейса
+
+The bridge embeds English, Russian and Chinese catalogs under `Localization/`.
+Studio passes `--language=ru`, `--language=en` or `--language=zh` at launch.
+Standalone launch reads `AhaKeySelectedLanguage` from
+`~/.ahakey/preferences.properties`, then falls back to the Windows UI language.
+Restart the bridge after changing language. Unknown locales use English.
+The preference file is read only; wire protocol and diagnostic IDs are unchanged.
+
+```powershell
+MSBuild .\BLE_tcp_driver.csproj /restore /p:Configuration=Release
+.\tests\Test-Localization.ps1
+.\bin\Release\BLE_tcp_driver.exe --language=ru --show
+```
+
+Keep each key and its format placeholders present in all three RESX files.
+The resources are embedded in the executable; no language DLLs need deployment.
diff --git a/BLE_tcp_bridge/tests/LocalizationTests.cs b/BLE_tcp_bridge/tests/LocalizationTests.cs
new file mode 100644
index 00000000..ccb51fc4
--- /dev/null
+++ b/BLE_tcp_bridge/tests/LocalizationTests.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Resources;
+using System.Text.RegularExpressions;
+
+internal static class LocalizationTests
+{
+ private static Type text;
+ private static object Call(string name, params object[] arguments) =>
+ text.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, arguments);
+ private static void Check(bool condition, string message)
+ {
+ if (!condition) throw new Exception(message);
+ }
+ private static string T(string key, params object[] arguments) => (string)Call("T", key, arguments);
+
+ [STAThread]
+ private static int Main(string[] args)
+ {
+ string temp = Path.Combine(Path.GetTempPath(), "ahakey-locale-" + Guid.NewGuid());
+ Directory.CreateDirectory(temp);
+ string preferences = Path.Combine(temp, "preferences.properties");
+ try
+ {
+ var assembly = Assembly.LoadFrom(Path.GetFullPath(args[0]));
+ text = assembly.GetType("BLE_tcp_driver.BridgeText", true);
+ var english = new ResourceManager("BLE_tcp_driver.Messages.en", assembly)
+ .GetResourceSet(CultureInfo.InvariantCulture, true, false);
+ var placeholders = new Regex(@"\{\d+(?:[^}]*)\}");
+ foreach (string language in new[] { "en", "ru", "zh" })
+ {
+ var catalog = new ResourceManager("BLE_tcp_driver.Messages." + language, assembly)
+ .GetResourceSet(CultureInfo.InvariantCulture, true, false);
+ Check(catalog.Cast().Count() == english.Cast().Count(), language + " key count");
+ foreach (DictionaryEntry entry in english)
+ {
+ string value = catalog.GetString((string)entry.Key);
+ Check(!string.IsNullOrWhiteSpace(value), language + ": " + entry.Key);
+ Check(placeholders.Matches((string)entry.Value).Cast().Select(m => m.Value)
+ .SequenceEqual(placeholders.Matches(value).Cast().Select(m => m.Value)), "Placeholders: " + entry.Key);
+ if (language == "ru") Check(!Regex.IsMatch(value, @"[\u4e00-\u9fff]"), "Untranslated: " + entry.Key);
+ string.Format(CultureInfo.InvariantCulture, value, Enumerable.Range(0, 10).Cast