diff --git a/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs b/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs
index 1099b8f6..e72583e5 100644
--- a/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs
@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
+using OpenClawTray.Helpers;
using OpenClawTray.Services;
using System;
using System.Collections.Generic;
@@ -413,7 +414,7 @@ private UIElement RenderArrayField(string path, string label, string? descriptio
Children =
{
new FontIcon { Glyph = "\uE710", FontSize = 12 },
- new TextBlock { Text = "Add item" }
+ new TextBlock { Text = LocalizationHelper.GetString("SchemaConfigEditor_AddItem") }
}
},
Margin = new Thickness(0, 4, 0, 0)
diff --git a/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml
index 30062e5c..c5e82ef2 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml
+++ b/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml
@@ -21,7 +21,7 @@
Background="#20FF4444">
-
@@ -52,7 +52,7 @@
-
+
diff --git a/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml.cs
index fb0d596a..8858c022 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml.cs
@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using OpenClaw.Shared;
+using OpenClawTray.Helpers;
using OpenClawTray.Services;
using OpenClawTray.Windows;
@@ -44,7 +45,7 @@ public void PopulateAgentFilter(HubWindow hub)
{
AgentFilterCombo.SelectionChanged -= OnAgentFilterComboChanged;
AgentFilterCombo.Items.Clear();
- AgentFilterCombo.Items.Add(new ComboBoxItem { Content = "All Agents", Tag = "" });
+ AgentFilterCombo.Items.Add(new ComboBoxItem { Content = LocalizationHelper.GetString("AgentEventsPage_AllAgents"), Tag = "" });
foreach (var id in hub.GetAgentIds())
AgentFilterCombo.Items.Add(new ComboBoxItem { Content = id, Tag = id });
AgentFilterCombo.SelectedIndex = 0;
@@ -178,7 +179,7 @@ private void ApplyFilter()
EventsList.Visibility = list.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
EmptyState.Visibility = list.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
CountText.Text = $"({_allEvents.Count})";
- StatusText.Text = $"{list.Count} of {_allEvents.Count} events";
+ StatusText.Text = string.Format(LocalizationHelper.GetString("AgentEventsPage_EventsStatus"), list.Count, _allEvents.Count);
}
private void OnClear(object sender, RoutedEventArgs e)
diff --git a/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs
index fc8e1854..74f6f857 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs
@@ -779,7 +779,7 @@ private FrameworkElement BuildBody(ChannelRecord record)
var unavailableGuide = BuildSetupGuide(record);
if (unavailableGuide != null) stack.Children.Add(unavailableGuide);
else stack.Children.Add(BuildInfoText(
- "This channel requires a macOS host. It can't be configured from a Windows machine."));
+ LocalizationHelper.GetString("ChannelsPage_UnavailableOnWindows")));
return stack;
}
@@ -819,7 +819,7 @@ private FrameworkElement BuildBody(ChannelRecord record)
// channels it reads "Configuration" because that's where the
// channel becomes configured in the first place.
var inlineForm = BuildInlineConfigForm(record);
- var configSectionTitle = record.IsConfigured ? "Replace credentials" : "Configuration";
+ var configSectionTitle = record.IsConfigured ? LocalizationHelper.GetString("ChannelsPage_ReplaceCredentials") : LocalizationHelper.GetString("ChannelsPage_Configuration");
stack.Children.Add(BuildSection(configSectionTitle, inlineForm));
// "Install plugin on your gateway" panel. Hidden entirely when the
@@ -865,23 +865,23 @@ private FrameworkElement BuildBody(ChannelRecord record)
string caption;
if (isQr && hasLogout)
{
- caption = "Unlink this device from the channel. Scan a fresh QR to reconnect — your account stays paired on your phone.";
+ caption = LocalizationHelper.GetString("ChannelsPage_CaptionQrLogout");
}
else if (hasStop && hasLogout)
{
- caption = "Pause the channel temporarily — credentials are kept so you can start it again. Or disconnect entirely to clear them.";
+ caption = LocalizationHelper.GetString("ChannelsPage_CaptionStopLogout");
}
else if (hasStart && hasLogout)
{
- caption = "Start the channel — it'll begin handling messages with the stored credentials. Or disconnect entirely to clear them.";
+ caption = LocalizationHelper.GetString("ChannelsPage_CaptionStartLogout");
}
else if (hasStop)
{
- caption = "Pause the channel — credentials are kept so you can start it again.";
+ caption = LocalizationHelper.GetString("ChannelsPage_CaptionStopOnly");
}
else if (hasStart)
{
- caption = "Start the channel — it'll begin handling messages with the stored credentials.";
+ caption = LocalizationHelper.GetString("ChannelsPage_CaptionStartOnly");
}
else
{
@@ -902,12 +902,12 @@ private FrameworkElement BuildBody(ChannelRecord record)
var buttonRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
if (hasStart)
- buttonRow.Children.Add(BuildHeaderActionButton(FluentIconCatalog.ChannelStart, "Start", record.Id, channelId => StartChannelAsync(channelId!)));
+ buttonRow.Children.Add(BuildHeaderActionButton(FluentIconCatalog.ChannelStart, LocalizationHelper.GetString("ChannelsPage_Start"), record.Id, channelId => StartChannelAsync(channelId!)));
if (hasStop)
{
var stopBtn = new Button
{
- Content = "Stop",
+ Content = LocalizationHelper.GetString("ChannelsPage_Stop"),
MinHeight = 32,
Padding = new Thickness(12, 5, 12, 5),
};
@@ -926,13 +926,13 @@ private FrameworkElement BuildBody(ChannelRecord record)
// Non-QR channels: Logout = clear credentials (destructive — label says so).
if (hasLogout && isQr)
{
- buttonRow.Children.Add(BuildHeaderActionButton(FluentIconCatalog.ChannelLogout, "Logout", record.Id, channelId => LogoutAsync(channelId!, isQr: true)));
+ buttonRow.Children.Add(BuildHeaderActionButton(FluentIconCatalog.ChannelLogout, LocalizationHelper.GetString("ChannelsPage_Logout"), record.Id, channelId => LogoutAsync(channelId!, isQr: true)));
}
else if (hasLogout && !isQr)
{
var disconnectBtn = new Button
{
- Content = "Disconnect and forget credentials",
+ Content = LocalizationHelper.GetString("ChannelsPage_DisconnectForget"),
MinHeight = 32,
Padding = new Thickness(12, 5, 12, 5),
};
@@ -950,7 +950,7 @@ private FrameworkElement BuildBody(ChannelRecord record)
}
stack.Children.Add(buttonRow);
- return BuildSection("Controls", stack);
+ return BuildSection(LocalizationHelper.GetString("ChannelsPage_Controls"), stack);
}
private static FrameworkElement BuildSection(string title, FrameworkElement content)
@@ -1061,14 +1061,14 @@ private static FrameworkElement BuildSection(string title, FrameworkElement cont
private static (string? Text, string? Url) ResolveExternalHelpLink(string channelId) =>
channelId.ToLowerInvariant() switch
{
- "whatsapp" => ("WhatsApp Linked devices help →", "https://faq.whatsapp.com/378279004439436/"),
- "signal" => ("Signal Linked devices help →", "https://support.signal.org/hc/en-us/articles/360007320551"),
- "telegram" => ("How to create a Telegram bot →", "https://core.telegram.org/bots/features#botfather"),
- "discord" => ("How to create a Discord webhook →", "https://support.discord.com/hc/en-us/articles/228383668"),
- "googlechat" => ("How to add a Google Chat webhook →", "https://developers.google.com/chat/how-tos/webhooks"),
- "slack" => ("Slack app dashboard →", "https://api.slack.com/apps"),
- "nostr" => ("About Nostr →", "https://nostr.com/"),
- "imessage" => ("About macOS Messages →", "https://support.apple.com/guide/messages/welcome/mac"),
+ "whatsapp" => (LocalizationHelper.GetString("ChannelsPage_HelpWhatsApp"), "https://faq.whatsapp.com/378279004439436/"),
+ "signal" => (LocalizationHelper.GetString("ChannelsPage_HelpSignal"), "https://support.signal.org/hc/en-us/articles/360007320551"),
+ "telegram" => (LocalizationHelper.GetString("ChannelsPage_HelpTelegram"), "https://core.telegram.org/bots/features#botfather"),
+ "discord" => (LocalizationHelper.GetString("ChannelsPage_HelpDiscord"), "https://support.discord.com/hc/en-us/articles/228383668"),
+ "googlechat" => (LocalizationHelper.GetString("ChannelsPage_HelpGoogleChat"), "https://developers.google.com/chat/how-tos/webhooks"),
+ "slack" => (LocalizationHelper.GetString("ChannelsPage_HelpSlack"), "https://api.slack.com/apps"),
+ "nostr" => (LocalizationHelper.GetString("ChannelsPage_HelpNostr"), "https://nostr.com/"),
+ "imessage" => (LocalizationHelper.GetString("ChannelsPage_HelpIMessage"), "https://support.apple.com/guide/messages/welcome/mac"),
_ => (null, null),
};
@@ -1080,67 +1080,64 @@ private static (string? Text, string? Url) ResolveExternalHelpLink(string channe
private static (string? Headline, string[]? Steps) ResolveSetupGuide(string channelId) =>
channelId.ToLowerInvariant() switch
{
- "whatsapp" => ("Link your WhatsApp phone", new[]
+ "whatsapp" => (LocalizationHelper.GetString("ChannelsPage_GuideWhatsAppHeadline"), new[]
{
- "Click \"Show QR\" in the Linking section below.",
- "On your phone: WhatsApp → Settings → Linked devices → Link a device.",
- "Scan the QR code that appears here.",
+ LocalizationHelper.GetString("ChannelsPage_GuideWhatsAppStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideWhatsAppStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideWhatsAppStep3"),
}),
- "signal" => ("Link your Signal phone", new[]
+ "signal" => (LocalizationHelper.GetString("ChannelsPage_GuideSignalHeadline"), new[]
{
- "Click \"Show QR\" in the Linking section below.",
- "On your phone: Signal → Settings → Linked devices → Link new device.",
- "Scan the QR code that appears here.",
+ LocalizationHelper.GetString("ChannelsPage_GuideSignalStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideSignalStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideSignalStep3"),
}),
- "telegram" => ("Connect Telegram via a bot", new[]
+ "telegram" => (LocalizationHelper.GetString("ChannelsPage_GuideTelegramHeadline"), new[]
{
- "Open Telegram and send a message to @BotFather.",
- "Send /newbot and follow the prompts. Copy the bot token at the end.",
- "Paste the token into the Configuration form below.",
- "Click \"Save and start\". The channel will start automatically.",
+ LocalizationHelper.GetString("ChannelsPage_GuideTelegramStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideTelegramStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideTelegramStep3"),
+ LocalizationHelper.GetString("ChannelsPage_GuideTelegramStep4"),
}),
- "discord" => ("Connect Discord via a webhook", new[]
+ "discord" => (LocalizationHelper.GetString("ChannelsPage_GuideDiscordHeadline"), new[]
{
- "Open your Discord server settings → Integrations → Webhooks.",
- "Click \"New Webhook\", give it a name, and copy the webhook URL.",
- "Paste the URL into the Configuration form below.",
- "Click \"Save and start\".",
+ LocalizationHelper.GetString("ChannelsPage_GuideDiscordStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideDiscordStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideDiscordStep3"),
+ LocalizationHelper.GetString("ChannelsPage_GuideDiscordStep4"),
}),
- "googlechat" => ("Connect Google Chat via a webhook", new[]
+ "googlechat" => (LocalizationHelper.GetString("ChannelsPage_GuideGoogleChatHeadline"), new[]
{
- "In Google Chat, open the space → Manage webhooks → Add webhook.",
- "Copy the webhook URL.",
- "Paste the URL into the Configuration form below.",
- "Click \"Save and start\".",
+ LocalizationHelper.GetString("ChannelsPage_GuideGoogleChatStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideGoogleChatStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideGoogleChatStep3"),
+ LocalizationHelper.GetString("ChannelsPage_GuideGoogleChatStep4"),
}),
- "slack" => ("Connect Slack via an app", new[]
+ "slack" => (LocalizationHelper.GetString("ChannelsPage_GuideSlackHeadline"), new[]
{
- "Create a Slack app at api.slack.com/apps and install it to your workspace.",
- "Copy the bot token (xoxb-…) and the signing secret.",
- "Paste both into the Configuration form below.",
- "Click \"Save and start\".",
+ LocalizationHelper.GetString("ChannelsPage_GuideSlackStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideSlackStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideSlackStep3"),
+ LocalizationHelper.GetString("ChannelsPage_GuideSlackStep4"),
}),
- "nostr" => ("Connect Nostr via relays", new[]
+ "nostr" => (LocalizationHelper.GetString("ChannelsPage_GuideNostrHeadline"), new[]
{
- "Generate or paste a private key (nsec).",
- "Pick one or more relay URLs (e.g. wss://relay.damus.io).",
- "Paste both into the Configuration form below.",
- "Click \"Save and start\".",
+ LocalizationHelper.GetString("ChannelsPage_GuideNostrStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideNostrStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideNostrStep3"),
+ LocalizationHelper.GetString("ChannelsPage_GuideNostrStep4"),
}),
- "imessage" => ("iMessage is macOS-only", new[]
+ "imessage" => (LocalizationHelper.GetString("ChannelsPage_GuideIMessageHeadline"), new[]
{
- "iMessage reads the local Messages.app database, which only exists on macOS.",
- "To use iMessage with OpenClaw, run the gateway on a Mac instead of this Windows host.",
- "All other channels in this list work fine from a Windows-hosted gateway.",
+ LocalizationHelper.GetString("ChannelsPage_GuideIMessageStep1"),
+ LocalizationHelper.GetString("ChannelsPage_GuideIMessageStep2"),
+ LocalizationHelper.GetString("ChannelsPage_GuideIMessageStep3"),
}),
- // Generic fallback for plugin channels we don't have explicit
- // guidance for. Better than leaving the section blank — at least
- // the user knows it's a plugin and where to find its settings.
- _ => ("Connect this channel", new[]
+ _ => (LocalizationHelper.GetString("ChannelsPage_GuideGenericHeadline"), new[]
{
- $"\"{channelId}\" is a plugin channel. Refer to its documentation for the fields it needs.",
- $"Use \"Open Config page\" in the Configuration section below to add settings under channels.{channelId}.",
- "Save the config; OpenClaw will start the channel automatically.",
+ string.Format(LocalizationHelper.GetString("ChannelsPage_GuideGenericStep1"), channelId),
+ string.Format(LocalizationHelper.GetString("ChannelsPage_GuideGenericStep2"), channelId),
+ LocalizationHelper.GetString("ChannelsPage_GuideGenericStep3"),
}),
};
@@ -1170,65 +1167,65 @@ private sealed record ConfigField(
{
new ConfigField(
"channels.telegram.botToken",
- "Bot token",
+ LocalizationHelper.GetString("ChannelsPage_FieldBotToken"),
"123456:ABCdef...",
Sensitive: true,
Required: true,
- HelpText: "Get from @BotFather (/newbot)."),
+ HelpText: LocalizationHelper.GetString("ChannelsPage_HelpBotToken")),
},
"discord" => new[]
{
new ConfigField(
"channels.discord.webhookUrl",
- "Webhook URL",
+ LocalizationHelper.GetString("ChannelsPage_FieldWebhookUrl"),
"https://discord.com/api/webhooks/...",
Sensitive: true,
Required: true,
- HelpText: "Server Settings → Integrations → Webhooks → New Webhook."),
+ HelpText: LocalizationHelper.GetString("ChannelsPage_HelpWebhookDiscord")),
},
"googlechat" => new[]
{
new ConfigField(
"channels.googlechat.webhookUrl",
- "Webhook URL",
+ LocalizationHelper.GetString("ChannelsPage_FieldWebhookUrl"),
"https://chat.googleapis.com/v1/spaces/...",
Sensitive: true,
Required: true,
- HelpText: "Open a space → Manage webhooks → Add webhook."),
+ HelpText: LocalizationHelper.GetString("ChannelsPage_HelpWebhookGoogleChat")),
},
"slack" => new[]
{
new ConfigField(
"channels.slack.botToken",
- "Bot token",
+ LocalizationHelper.GetString("ChannelsPage_FieldBotToken"),
"xoxb-...",
Sensitive: true,
Required: true,
- HelpText: "OAuth tokens from your Slack app."),
+ HelpText: LocalizationHelper.GetString("ChannelsPage_HelpSlackBotToken")),
new ConfigField(
"channels.slack.signingSecret",
- "Signing secret",
+ LocalizationHelper.GetString("ChannelsPage_FieldSigningSecret"),
"",
Sensitive: true,
Required: true,
- HelpText: "Basic Information → App Credentials."),
+ HelpText: LocalizationHelper.GetString("ChannelsPage_HelpSlackSigningSecret")),
},
"nostr" => new[]
{
new ConfigField(
"channels.nostr.nsec",
- "Private key (nsec)",
+ LocalizationHelper.GetString("ChannelsPage_FieldPrivateKey"),
"nsec1...",
Sensitive: true,
Required: true),
new ConfigField(
"channels.nostr.relays",
- "Relay URLs",
+ LocalizationHelper.GetString("ChannelsPage_FieldRelayUrls"),
"wss://relay.damus.io",
Sensitive: false,
Required: true,
Multiline: true,
- HelpText: "One per line."),
+ HelpText: LocalizationHelper.GetString("ChannelsPage_HelpRelayUrls")),
},
_ => null,
};
@@ -1339,12 +1336,12 @@ private FrameworkElement BuildInlineConfigForm(ChannelRecord record)
var actionRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Thickness(0, 4, 0, 0) };
var saveBtn = new Button
{
- Content = record.IsConfigured ? "Save changes" : "Save and start",
+ Content = record.IsConfigured ? LocalizationHelper.GetString("ChannelsPage_SaveChanges") : LocalizationHelper.GetString("ChannelsPage_SaveAndStart"),
Style = (Style)Application.Current.Resources["AccentButtonStyle"],
};
var openConfigBtn = new Button
{
- Content = "Open Config page",
+ Content = LocalizationHelper.GetString("ChannelsPage_OpenConfigPage"),
};
openConfigBtn.Click += (_, _) => ((IAppCommands)CurrentApp).Navigate("config");
actionRow.Children.Add(saveBtn);
@@ -1361,8 +1358,8 @@ async Task SaveAsync()
var client = CurrentApp.GatewayClient;
if (client == null)
{
- _pendingSaveBanner = (record.Id, "Not connected",
- "Connect to a gateway before saving channel config.",
+ _pendingSaveBanner = (record.Id, LocalizationHelper.GetString("ChannelsPage_BannerNotConnectedTitle"),
+ LocalizationHelper.GetString("ChannelsPage_BannerNotConnectedMessage"),
InfoBarSeverity.Error);
ApplyPendingSaveBanner();
return;
@@ -1381,8 +1378,8 @@ async Task SaveAsync()
};
if (field.Required && string.IsNullOrWhiteSpace(raw))
{
- _pendingSaveBanner = (record.Id, "Missing field",
- $"{field.Label} is required.",
+ _pendingSaveBanner = (record.Id, LocalizationHelper.GetString("ChannelsPage_BannerMissingFieldTitle"),
+ string.Format(LocalizationHelper.GetString("ChannelsPage_BannerMissingFieldMessage"), field.Label),
InfoBarSeverity.Error);
ApplyPendingSaveBanner();
return;
@@ -1397,8 +1394,8 @@ async Task SaveAsync()
saveBtn.IsEnabled = false;
try
{
- _pendingSaveBanner = (record.Id, $"Saving {record.Id}…",
- $"Writing {values.Count} field(s) and enabling the channel.",
+ _pendingSaveBanner = (record.Id, string.Format(LocalizationHelper.GetString("ChannelsPage_BannerSavingTitle"), record.Id),
+ string.Format(LocalizationHelper.GetString("ChannelsPage_BannerSavingMessage"), values.Count),
InfoBarSeverity.Informational);
ApplyPendingSaveBanner();
@@ -1409,8 +1406,8 @@ async Task SaveAsync()
// the gateway's actual response.
if (!await EnsureConfigLoadedAsync())
{
- _pendingSaveBanner = (record.Id, $"Couldn't load gateway config",
- "The gateway didn't return its current config — can't safely save without it. Try Refresh and save again.",
+ _pendingSaveBanner = (record.Id, LocalizationHelper.GetString("ChannelsPage_BannerCantLoadConfigTitle"),
+ LocalizationHelper.GetString("ChannelsPage_BannerCantLoadConfigMessage"),
InfoBarSeverity.Error);
ApplyPendingSaveBanner();
return;
@@ -1426,8 +1423,8 @@ async Task SaveAsync()
// true when the snapshot is populated — but defend
// against a race where the snapshot was reset between
// the load returning and this read.
- _pendingSaveBanner = (record.Id, $"Couldn't load gateway config",
- "The gateway's config was cleared mid-save. Try Refresh and save again.",
+ _pendingSaveBanner = (record.Id, LocalizationHelper.GetString("ChannelsPage_BannerCantLoadConfigTitle"),
+ LocalizationHelper.GetString("ChannelsPage_BannerConfigClearedMessage"),
InfoBarSeverity.Error);
ApplyPendingSaveBanner();
return;
@@ -1442,7 +1439,7 @@ async Task SaveAsync()
if (buildResult.BlockedReason != null)
{
- _pendingSaveBanner = (record.Id, $"Save blocked for {record.Id}",
+ _pendingSaveBanner = (record.Id, string.Format(LocalizationHelper.GetString("ChannelsPage_BannerSaveBlockedTitle"), record.Id),
buildResult.BlockedReason,
InfoBarSeverity.Warning);
ApplyPendingSaveBanner();
@@ -1453,30 +1450,29 @@ async Task SaveAsync()
if (!patchResult.Ok)
{
- var detail = patchResult.Error ?? "The gateway didn't respond.";
- var title = $"Save failed for {record.Id}";
+ var detail = patchResult.Error ?? LocalizationHelper.GetString("ChannelsPage_BannerGatewayNoResponse");
+ var title = string.Format(LocalizationHelper.GetString("ChannelsPage_BannerSaveFailedTitle"), record.Id);
if (patchResult.LooksLikeStaleBaseHash)
{
// Config changed elsewhere. Refresh the cache so the
// next retry uses a fresh baseHash.
_ = client.RequestConfigAsync();
_pendingSaveBanner = (record.Id, title,
- $"Your gateway config changed elsewhere (e.g., from the Config page). " +
- $"We refreshed the cache — try Save again. Details: {detail}",
+ string.Format(LocalizationHelper.GetString("ChannelsPage_BannerStaleConfigMessage"), detail),
InfoBarSeverity.Warning);
}
else
{
_pendingSaveBanner = (record.Id, title,
- $"{detail} If this looks like a wire-format mismatch, open the Config page for direct JSON editing.",
+ string.Format(LocalizationHelper.GetString("ChannelsPage_BannerSaveFailedMessage"), detail),
InfoBarSeverity.Error);
}
ApplyPendingSaveBanner();
return;
}
- _pendingSaveBanner = (record.Id, $"{record.Id} config saved",
- "Waiting for the gateway to confirm the channel is running…",
+ _pendingSaveBanner = (record.Id, string.Format(LocalizationHelper.GetString("ChannelsPage_BannerConfigSavedTitle"), record.Id),
+ LocalizationHelper.GetString("ChannelsPage_BannerConfigSavedMessage"),
InfoBarSeverity.Success);
ApplyPendingSaveBanner();
// Invalidate the snapshot so a rapid second save MUST wait
@@ -1933,15 +1929,15 @@ private FrameworkElement BuildConfigPlaceholder(ChannelRecord record)
stack.Children.Add(new TextBlock
{
Text = record.IsConfigured
- ? $"Edit this channel's settings in the gateway Config page."
- : $"After following the steps above, save the config to start the channel.",
+ ? LocalizationHelper.GetString("ChannelsPage_ConfigPlaceholderConfigured")
+ : LocalizationHelper.GetString("ChannelsPage_ConfigPlaceholderUnconfigured"),
Style = (Style)Application.Current.Resources["BodyTextBlockStyle"],
Foreground = (Brush)Application.Current.Resources["TextFillColorSecondaryBrush"],
TextWrapping = TextWrapping.Wrap,
});
var btn = new Button
{
- Content = "Open Config page",
+ Content = LocalizationHelper.GetString("ChannelsPage_OpenConfigPage"),
HorizontalAlignment = HorizontalAlignment.Left,
};
btn.Click += (_, _) =>
diff --git a/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs
index 37aaa0f5..b767839c 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs
@@ -339,7 +339,7 @@ private void ShowMissingChatCredentialError()
WebView.Visibility = Visibility.Collapsed;
PlaceholderPanel.Visibility = Visibility.Collapsed;
ErrorPanel.Visibility = Visibility.Visible;
- ErrorText.Text = "Open Connection settings to finish pairing with a gateway.";
+ ErrorText.Text = LocalizationHelper.GetString("ChatPage_OpenConnectionSettings");
}
private void StopWebViewNavigation()
@@ -380,7 +380,7 @@ private async Task InitializeWebViewAsync(SettingsManager settings)
{
PlaceholderPanel.Visibility = Visibility.Collapsed;
ErrorPanel.Visibility = Visibility.Visible;
- ErrorText.Text = "Open Connection settings to finish pairing with a gateway.";
+ ErrorText.Text = LocalizationHelper.GetString("ChatPage_OpenConnectionSettings");
return;
}
@@ -388,7 +388,7 @@ private async Task InitializeWebViewAsync(SettingsManager settings)
{
PlaceholderPanel.Visibility = Visibility.Collapsed;
ErrorPanel.Visibility = Visibility.Visible;
- ErrorText.Text = "Gateway pairing is not complete. Open Connection settings to finish pairing.";
+ ErrorText.Text = LocalizationHelper.GetString("ChatPage_GatewayPairingIncomplete");
return;
}
@@ -406,7 +406,7 @@ private async Task InitializeWebViewAsync(SettingsManager settings)
ErrorPanel.Visibility = Visibility.Collapsed;
WebView.Visibility = Visibility.Collapsed;
WaitingPanel.Visibility = Visibility.Visible;
- WaitingStatusText.Text = "The gateway is connected; the chat surface is still coming online.";
+ WaitingStatusText.Text = LocalizationHelper.GetString("ChatPage_ChatSurfaceComingOnline");
RetryChatButton.Visibility = Visibility.Collapsed;
LoadingRing.IsActive = true;
LoadingRing.Visibility = Visibility.Visible;
@@ -440,7 +440,7 @@ private async Task InitializeWebViewAsync(SettingsManager settings)
{
WebView.Visibility = Visibility.Collapsed;
ErrorPanel.Visibility = Visibility.Visible;
- ErrorText.Text = $"Cannot connect to gateway at {credential.GatewayUrl}\n\nMake sure the gateway is running.";
+ ErrorText.Text = string.Format(LocalizationHelper.GetString("ChatPage_CannotConnectToGateway"), credential.GatewayUrl);
}
};
WebView.CoreWebView2.NavigationCompleted += _navCompletedHandler;
@@ -464,7 +464,7 @@ private async Task InitializeWebViewAsync(SettingsManager settings)
PlaceholderPanel.Visibility = Visibility.Collapsed;
WebView.Visibility = Visibility.Collapsed;
ErrorPanel.Visibility = Visibility.Visible;
- ErrorText.Text = $"WebView2 failed to initialize:\n{ex.Message}";
+ ErrorText.Text = string.Format(LocalizationHelper.GetString("ChatPage_WebView2InitFailed"), ex.Message);
}
}
@@ -481,7 +481,7 @@ private async Task NavigateWhenChatReadyAsync(
var ready = await ChatNavigationReadiness.WaitForOperatorHandshakeAsync(connectionManager, TimeSpan.FromSeconds(30), cancellationToken);
if (!ready)
{
- ShowChatReadinessFailure("Timed out waiting for the gateway operator handshake. Retry once the gateway is ready.");
+ ShowChatReadinessFailure(LocalizationHelper.GetString("ChatPage_TimedOutHandshake"));
Logger.Warn("[ChatPage] Timed out waiting for operator handshake before chat navigation");
return;
}
@@ -490,12 +490,12 @@ private async Task NavigateWhenChatReadyAsync(
ready = await ProbeChatSurfaceAsync(_chatUrl!, TimeSpan.FromSeconds(30), cancellationToken);
if (!ready)
{
- ShowChatReadinessFailure($"Timed out waiting for chat at {gatewayUrl}. Retry once the gateway is ready.");
+ ShowChatReadinessFailure(string.Format(LocalizationHelper.GetString("ChatPage_TimedOutChat"), gatewayUrl));
Logger.Warn("[ChatPage] Timed out waiting for chat HTTP surface before navigation");
return;
}
- WaitingStatusText.Text = "Chat is ready; starting your first hatching conversation…";
+ WaitingStatusText.Text = LocalizationHelper.GetString("ChatPage_ChatReady");
var bootstrapped = await OnboardingChatBootstrapper.BootstrapAsync(
connectionManager?.OperatorClient,
((App)Application.Current).Settings,
@@ -520,7 +520,7 @@ private async Task NavigateWhenChatReadyAsync(
}
catch (Exception ex)
{
- ShowChatReadinessFailure($"Chat failed to start:\n{ex.Message}");
+ ShowChatReadinessFailure(string.Format(LocalizationHelper.GetString("ChatPage_ChatFailedToStart"), ex.Message));
Logger.Warn($"[ChatPage] Chat readiness wait failed: {ex.Message}");
}
}
@@ -608,7 +608,7 @@ private static bool TryBuildChatUrl(string gatewayUrl, string token, out string
if (!GatewayUrlHelper.TryNormalizeWebSocketUrl(gatewayUrl, out var normalizedUrl) ||
!Uri.TryCreate(normalizedUrl, UriKind.Absolute, out var gatewayUri))
{
- errorMessage = $"Invalid gateway URL: {gatewayUrl}";
+ errorMessage = string.Format(LocalizationHelper.GetString("ChatPage_InvalidGatewayUrl"), gatewayUrl);
return false;
}
@@ -793,7 +793,7 @@ private async Task PickAndAttachFileAsync()
}
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle((Window)_hub!);
- var path = await Win32FilePickerHelper.PickSingleFileAsync(hwnd, "Attach file");
+ var path = await Win32FilePickerHelper.PickSingleFileAsync(hwnd, LocalizationHelper.GetString("ChatPage_AttachFile"));
if (path is null)
{
@@ -822,9 +822,9 @@ private async Task ShowAttachmentErrorAsync(string message)
{
var dialog = new ContentDialog
{
- Title = "Cannot attach file",
+ Title = LocalizationHelper.GetString("ChatPage_CannotAttachFile"),
Content = message,
- CloseButtonText = "OK",
+ CloseButtonText = LocalizationHelper.GetString("ChatPage_OK"),
DefaultButton = ContentDialogButton.Close,
XamlRoot = this.XamlRoot
};
diff --git a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml
index 8addc3de..b8579a1f 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml
+++ b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml
@@ -165,7 +165,8 @@
Visibility="Collapsed"
Click="OnStripPrimaryClicked"
AutomationProperties.AutomationId="StripPrimaryAction"/>
-
-
-
-
-
-
+
-
-
- 0)
{
- var label = n == 1 ? "1 client" : $"{n} clients";
+ var label = n == 1 ? LocalizationHelper.GetString("ConnectionPage_ClientSingular") : string.Format(LocalizationHelper.GetString("ConnectionPage_ClientsPlural"), n);
chips.Add(BuildGlanceChip(Helpers.FluentIconCatalog.People, label, neutral: true));
}
@@ -594,7 +594,9 @@ private void ApplyGlanceChips(ConnectionPagePlan plan, bool show)
// is configured. Counts linked-and-ok channels.
if (channelTotal > 0)
{
- var label = $"{channelOk}/{channelTotal} channel{(channelTotal == 1 ? "" : "s")}";
+ var label = channelTotal == 1
+ ? string.Format(LocalizationHelper.GetString("ConnectionPage_ChannelsSingular"), channelOk, channelTotal)
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_ChannelsPlural"), channelOk, channelTotal);
chips.Add(BuildGlanceChip(Helpers.FluentIconCatalog.Channels, label, neutral: channelOk == channelTotal));
}
@@ -603,16 +605,16 @@ private void ApplyGlanceChips(ConnectionPagePlan plan, bool show)
if (cost?.Daily is { Count: > 0 })
{
var label = todayAmount > 0
- ? $"${todayAmount:0.00} today"
- : "$0.00 today";
+ ? string.Format(LocalizationHelper.GetString("ConnectionPage_CostToday"), todayAmount.ToString("0.00"))
+ : LocalizationHelper.GetString("ConnectionPage_CostTodayZero");
chips.Add(BuildGlanceChip(Helpers.FluentIconCatalog.Money, label, neutral: true));
}
// 6. Shared token — click to copy.
if (hasSharedToken)
{
- var chip = BuildGlanceChip(Helpers.FluentIconCatalog.Lock, "Shared token", neutral: true);
- ToolTipService.SetToolTip(chip, "Tap to copy shared token");
+ var chip = BuildGlanceChip(Helpers.FluentIconCatalog.Lock, LocalizationHelper.GetString("ConnectionPage_SharedTokenChip"), neutral: true);
+ ToolTipService.SetToolTip(chip, LocalizationHelper.GetString("ConnectionPage_TapToCopySharedToken"));
chip.Tapped += (_, _) =>
{
ClipboardHelper.CopyText(sharedToken!);
@@ -633,28 +635,28 @@ private void ApplyGlanceChips(ConnectionPagePlan plan, bool show)
private static string? ClassifyTopology(GatewayRecord? rec)
{
if (rec == null) return null;
- if (rec.SshTunnel != null) return "via SSH tunnel";
+ if (rec.SshTunnel != null) return LocalizationHelper.GetString("ConnectionPage_TopologyViaSshTunnel");
if (string.IsNullOrEmpty(rec.Url)) return null;
try
{
var uri = new Uri(rec.Url);
var host = uri.Host;
- if (host == "localhost" || host == "127.0.0.1" || host == "::1") return "Local";
- if (host.EndsWith(".ts.net", StringComparison.OrdinalIgnoreCase)) return "Tailscale";
- if (host.EndsWith(".local", StringComparison.OrdinalIgnoreCase)) return "LAN";
+ if (host == "localhost" || host == "127.0.0.1" || host == "::1") return LocalizationHelper.GetString("ConnectionPage_TopologyLocal");
+ if (host.EndsWith(".ts.net", StringComparison.OrdinalIgnoreCase)) return LocalizationHelper.GetString("ConnectionPage_TopologyTailscale");
+ if (host.EndsWith(".local", StringComparison.OrdinalIgnoreCase)) return LocalizationHelper.GetString("ConnectionPage_TopologyLAN");
// RFC1918 private ranges
if (host.StartsWith("10.") || host.StartsWith("192.168.") ||
(host.StartsWith("172.") && IsPrivate172(host)))
- return "LAN";
+ return LocalizationHelper.GetString("ConnectionPage_TopologyLAN");
// Tailnet CGNAT range 100.64.0.0/10
if (host.StartsWith("100."))
{
var parts = host.Split('.');
if (parts.Length >= 2 && int.TryParse(parts[1], out var second) &&
second >= 64 && second <= 127)
- return "Tailscale";
+ return LocalizationHelper.GetString("ConnectionPage_TopologyTailscale");
}
- return "Remote";
+ return LocalizationHelper.GetString("ConnectionPage_TopologyRemote");
}
catch
{
@@ -720,25 +722,25 @@ private void ApplyOperatorCard(ConnectionPagePlan plan)
Helpers.FluentIconCatalog.StatusOk,
"SystemFillColorSuccessBrush",
activeSessions == 1
- ? "Active · 1 session"
- : $"Active · {activeSessions} sessions"),
+ ? LocalizationHelper.GetString("ConnectionPage_OperatorActiveOneSession")
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_OperatorActiveSessions"), activeSessions)),
OperatorCardState.Active => (
Helpers.FluentIconCatalog.StatusOk,
"SystemFillColorSuccessBrush",
- "Active · no current sessions"),
+ LocalizationHelper.GetString("ConnectionPage_OperatorActiveNoSessions")),
OperatorCardState.Idle => (
Helpers.FluentIconCatalog.CapabilityOff,
"SystemFillColorNeutralBrush",
- "Disconnected"),
+ LocalizationHelper.GetString("ConnectionPage_OperatorDisconnected")),
OperatorCardState.Connecting => (
Helpers.FluentIconCatalog.Sync,
"SystemFillColorCautionBrush",
- "Connecting…"),
+ LocalizationHelper.GetString("ConnectionPage_OperatorConnecting")),
OperatorCardState.Paused => (
Helpers.FluentIconCatalog.Sync,
"SystemFillColorCautionBrush",
- "Reconnecting…"),
- _ => (Helpers.FluentIconCatalog.CapabilityOff, "SystemFillColorNeutralBrush", "Disconnected"),
+ LocalizationHelper.GetString("ConnectionPage_OperatorReconnecting")),
+ _ => (Helpers.FluentIconCatalog.CapabilityOff, "SystemFillColorNeutralBrush", LocalizationHelper.GetString("ConnectionPage_OperatorDisconnected")),
};
OperatorStatusIcon.Glyph = statusGlyph;
OperatorStatusIcon.Foreground = ResolveBrush(statusBrushKey);
@@ -773,11 +775,11 @@ or NodeCardState.OnNodeRateLimited
or NodeCardState.OnNodeError;
var bodyText = plan.NodeCard switch
{
- NodeCardState.OnPermissionsIncomplete => "No capabilities enabled. Pick what to share in Permissions.",
- NodeCardState.OnNodePairingRequired => "Awaiting approval on the gateway host.",
- NodeCardState.OnNodeRejected => "Pairing was rejected. Re-pair from Add Gateway.",
- NodeCardState.OnNodeRateLimited => "Rate-limited by the gateway. Will retry after cooldown.",
- NodeCardState.OnNodeError => plan.NodeErrorDetail ?? "Node reported an error.",
+ NodeCardState.OnPermissionsIncomplete => LocalizationHelper.GetString("ConnectionPage_NodeBodyNoCapabilities"),
+ NodeCardState.OnNodePairingRequired => LocalizationHelper.GetString("ConnectionPage_NodeBodyAwaitingApproval"),
+ NodeCardState.OnNodeRejected => LocalizationHelper.GetString("ConnectionPage_NodeBodyPairingRejected"),
+ NodeCardState.OnNodeRateLimited => LocalizationHelper.GetString("ConnectionPage_NodeBodyRateLimited"),
+ NodeCardState.OnNodeError => plan.NodeErrorDetail ?? LocalizationHelper.GetString("ConnectionPage_NodeBodyError"),
_ => "",
};
var bodyBrushKey = plan.NodeCard switch
@@ -795,32 +797,32 @@ or NodeCardState.OnNodeRateLimited
NodeCardState.OnHealthy => (
Helpers.FluentIconCatalog.StatusOk,
"SystemFillColorSuccessBrush",
- capCount == 1 ? "Node active · 1 capability" : $"Node active · {capCount} capabilities"),
+ capCount == 1 ? LocalizationHelper.GetString("ConnectionPage_NodeActiveOneCapability") : string.Format(LocalizationHelper.GetString("ConnectionPage_NodeActiveCapabilities"), capCount)),
NodeCardState.OnPermissionsIncomplete => (
Helpers.FluentIconCatalog.StatusWarn,
"SystemFillColorCautionBrush",
- "Node active · no capabilities enabled"),
+ LocalizationHelper.GetString("ConnectionPage_NodeActiveNoCapabilities")),
NodeCardState.OnNodePairingRequired => (
Helpers.FluentIconCatalog.Lock,
"SystemFillColorCautionBrush",
- "Awaiting pairing approval"),
+ LocalizationHelper.GetString("ConnectionPage_NodeAwaitingPairing")),
NodeCardState.OnNodeRejected => (
Helpers.FluentIconCatalog.StatusErr,
"SystemFillColorCriticalBrush",
- "Pairing rejected"),
+ LocalizationHelper.GetString("ConnectionPage_NodePairingRejected")),
NodeCardState.OnNodeRateLimited => (
Helpers.FluentIconCatalog.StatusWarn,
"SystemFillColorCautionBrush",
- "Rate-limited"),
+ LocalizationHelper.GetString("ConnectionPage_NodeRateLimited")),
NodeCardState.OnNodeError => (
Helpers.FluentIconCatalog.StatusErr,
"SystemFillColorCriticalBrush",
- "Node error"),
+ LocalizationHelper.GetString("ConnectionPage_NodeError")),
NodeCardState.Off => (
Helpers.FluentIconCatalog.CapabilityOff,
"SystemFillColorCriticalBrush",
- "Node mode disabled"),
- _ => (Helpers.FluentIconCatalog.CapabilityOff, "SystemFillColorCriticalBrush", "Node mode disabled"),
+ LocalizationHelper.GetString("ConnectionPage_NodeModeDisabledText")),
+ _ => (Helpers.FluentIconCatalog.CapabilityOff, "SystemFillColorCriticalBrush", LocalizationHelper.GetString("ConnectionPage_NodeModeDisabledText")),
};
NodeStatusIcon.Glyph = nodeGlyph;
NodeStatusIcon.Foreground = ResolveBrush(nodeBrushKey);
@@ -890,41 +892,41 @@ private void ApplyRecoveryBody(ConnectionPagePlan plan)
RecoveryHelpHeaderText.Text = plan.Recovery switch
{
- RecoveryCategory.Auth => "Re-pair this gateway:",
- RecoveryCategory.Pairing => "Awaiting approval:",
- RecoveryCategory.Tunnel => "Help us fix the SSH tunnel:",
- RecoveryCategory.Server => "Help us fix this connection:",
- _ => "Help us fix this connection:",
+ RecoveryCategory.Auth => LocalizationHelper.GetString("ConnectionPage_RecoveryHeaderAuth"),
+ RecoveryCategory.Pairing => LocalizationHelper.GetString("ConnectionPage_RecoveryHeaderPairing"),
+ RecoveryCategory.Tunnel => LocalizationHelper.GetString("ConnectionPage_RecoveryHeaderTunnel"),
+ RecoveryCategory.Server => LocalizationHelper.GetString("ConnectionPage_RecoveryHeaderServer"),
+ _ => LocalizationHelper.GetString("ConnectionPage_RecoveryHeaderServer"),
};
var bullets = plan.Recovery switch
{
RecoveryCategory.Auth => new[]
{
- "Get a fresh setup code from the gateway host.",
- "Or paste a new direct token below.",
+ LocalizationHelper.GetString("ConnectionPage_RecoveryAuthBullet1"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryAuthBullet2"),
},
RecoveryCategory.Pairing => new[]
{
- "Approve this client on the gateway host using the command below.",
- "Once approved, click Connect to resume.",
+ LocalizationHelper.GetString("ConnectionPage_RecoveryPairingBullet1"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryPairingBullet2"),
},
RecoveryCategory.Tunnel => new[]
{
- "Confirm SSH is reachable on the host.",
- "Restart the tunnel below if it stopped.",
+ LocalizationHelper.GetString("ConnectionPage_RecoveryTunnelBullet1"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryTunnelBullet2"),
},
RecoveryCategory.Server => new[]
{
- "Check the gateway logs on the host.",
- "If the gateway is partially up, open its dashboard.",
- "Edit gateway settings to change URL or token.",
+ LocalizationHelper.GetString("ConnectionPage_RecoveryServerBullet1"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryServerBullet2"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryServerBullet3"),
},
_ => new[]
{
- "Check that the gateway is running on the host.",
- "Check that you're on the same network or VPN.",
- "If using SSH tunnel: confirm it's up.",
+ LocalizationHelper.GetString("ConnectionPage_RecoveryDefaultBullet1"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryDefaultBullet2"),
+ LocalizationHelper.GetString("ConnectionPage_RecoveryDefaultBullet3"),
},
};
@@ -935,7 +937,7 @@ private void ApplyRecoveryBody(ConnectionPagePlan plan)
if (plan.Recovery == RecoveryCategory.Tunnel)
{
RecoveryTunnelBlock.Visibility = Visibility.Visible;
- RecoveryTunnelDetailText.Text = plan.RecoveryDetail ?? "SSH tunnel is down.";
+ RecoveryTunnelDetailText.Text = plan.RecoveryDetail ?? LocalizationHelper.GetString("ConnectionPage_SshTunnelIsDownText");
}
if (plan.Recovery == RecoveryCategory.Auth)
{
@@ -1055,8 +1057,10 @@ void Add(string label, bool enabled, bool warn = false, bool error = false)
private static string BuildCapabilityListString(IReadOnlyList? capabilities)
{
if (capabilities == null || capabilities.Count == 0)
- return "Providing no capabilities";
- return $"Providing {capabilities.Count} capabilit{(capabilities.Count == 1 ? "y" : "ies")}: {string.Join(", ", capabilities)}";
+ return LocalizationHelper.GetString("ConnectionPage_ProvidingNoCapabilities");
+ return capabilities.Count == 1
+ ? string.Format(LocalizationHelper.GetString("ConnectionPage_ProvidingCapabilitiesSingular"), string.Join(", ", capabilities))
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_ProvidingCapabilitiesPlural"), capabilities.Count, string.Join(", ", capabilities));
}
@@ -1133,17 +1137,17 @@ private void LoadSavedGateways()
SavedGatewaysList.ItemsSource = BuildSavedGatewayRowControls(items);
RecoverySavedList.ItemsSource = BuildSavedGatewayRowControls(items);
RecoverySavedHeaderText.Text = items.Count == 1
- ? "Saved gateways (1)"
- : $"Saved gateways ({items.Count})";
+ ? LocalizationHelper.GetString("ConnectionPage_SavedGatewaysSingular")
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_SavedGatewaysPlural"), items.Count);
}
private static string InferAuthModeLabel(GatewayRecord rec)
{
- if (!string.IsNullOrEmpty(rec.BootstrapToken)) return "bootstrap";
- if (!string.IsNullOrEmpty(rec.SharedGatewayToken)) return "shared token";
+ if (!string.IsNullOrEmpty(rec.BootstrapToken)) return LocalizationHelper.GetString("ConnectionPage_AuthModeBootstrap");
+ if (!string.IsNullOrEmpty(rec.SharedGatewayToken)) return LocalizationHelper.GetString("ConnectionPage_AuthModeSharedToken");
// No credential on the record itself → likely paired (device token
// stored in the DeviceIdentityStore for this gateway's identity dir).
- return "device token";
+ return LocalizationHelper.GetString("ConnectionPage_AuthModeDeviceToken");
}
private List BuildSavedGatewayRowControls(IEnumerable rows)
@@ -1183,16 +1187,16 @@ private static bool CanDisconnectFromBadge(OverallConnectionState state) =>
OverallConnectionState.Connected or
OverallConnectionState.Ready or
OverallConnectionState.Degraded =>
- (Helpers.FluentIconCatalog.StatusOk, "SystemFillColorSuccessBrush", "Connected"),
+ (Helpers.FluentIconCatalog.StatusOk, "SystemFillColorSuccessBrush", LocalizationHelper.GetString("ConnectionPage_BadgeConnected")),
OverallConnectionState.Connecting =>
- (Helpers.FluentIconCatalog.Sync, "SystemFillColorCautionBrush", "Connecting…"),
+ (Helpers.FluentIconCatalog.Sync, "SystemFillColorCautionBrush", LocalizationHelper.GetString("ConnectionPage_BadgeConnecting")),
OverallConnectionState.PairingRequired =>
- (Helpers.FluentIconCatalog.Lock, "SystemFillColorCautionBrush", "Awaiting approval"),
+ (Helpers.FluentIconCatalog.Lock, "SystemFillColorCautionBrush", LocalizationHelper.GetString("ConnectionPage_BadgeAwaitingApproval")),
OverallConnectionState.Disconnecting =>
- (Helpers.FluentIconCatalog.Sync, "TextFillColorSecondaryBrush", "Disconnecting…"),
+ (Helpers.FluentIconCatalog.Sync, "TextFillColorSecondaryBrush", LocalizationHelper.GetString("ConnectionPage_BadgeDisconnecting")),
// Idle and Error → no badge; caller renders [Connect].
// The status strip up top already carries the "broken / can't
@@ -1286,7 +1290,7 @@ private Border BuildSavedGatewayRowControl(SavedGatewayRow row)
{
sub.Children.Add(new TextBlock
{
- Text = "• via SSH",
+ Text = "• " + LocalizationHelper.GetString("ConnectionPage_ViaSSH"),
Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"],
Foreground = ResolveBrush("TextFillColorSecondaryBrush"),
});
@@ -1333,7 +1337,7 @@ private Border BuildSavedGatewayRowControl(SavedGatewayRow row)
{
var connectBtn = new Button
{
- Content = "Connect",
+ Content = LocalizationHelper.GetString("ConnectionPage_ConnectAction"),
Tag = row.Id,
VerticalAlignment = VerticalAlignment.Center,
};
@@ -1364,7 +1368,7 @@ private Border BuildSavedGatewayRowControl(SavedGatewayRow row)
Foreground = ResolveBrush("TextFillColorSecondaryBrush"),
};
var flyout = new MenuFlyout();
- var openDashboard = new MenuFlyoutItem { Text = "Open dashboard", Tag = row.Id };
+ var openDashboard = new MenuFlyoutItem { Text = LocalizationHelper.GetString("ConnectionPage_OpenDashboard"), Tag = row.Id };
openDashboard.Click += OnSavedRowOpenDashboard;
flyout.Items.Add(openDashboard);
if (row.HasHostTerminal)
@@ -1382,23 +1386,23 @@ private Border BuildSavedGatewayRowControl(SavedGatewayRow row)
// re-entering would race the connection manager.
if (CanDisconnectFromBadge(_lastSnapshot.OverallState))
{
- var disconnect = new MenuFlyoutItem { Text = "Disconnect", Tag = row.Id };
+ var disconnect = new MenuFlyoutItem { Text = LocalizationHelper.GetString("ConnectionPage_DisconnectAction"), Tag = row.Id };
disconnect.Click += OnDisconnect;
flyout.Items.Add(disconnect);
}
- var editActive = new MenuFlyoutItem { Text = "Edit", Tag = row.Id };
+ var editActive = new MenuFlyoutItem { Text = LocalizationHelper.GetString("ConnectionPage_Edit"), Tag = row.Id };
editActive.Click += OnSavedRowEdit;
flyout.Items.Add(editActive);
}
else
{
- var edit = new MenuFlyoutItem { Text = "Edit", Tag = row.Id };
+ var edit = new MenuFlyoutItem { Text = LocalizationHelper.GetString("ConnectionPage_Edit"), Tag = row.Id };
edit.Click += OnSavedRowEdit;
flyout.Items.Add(edit);
// Removing the active-but-disconnected row just clears the active
// pointer — safe.
flyout.Items.Add(new MenuFlyoutSeparator());
- var remove = new MenuFlyoutItem { Text = "Remove", Tag = row.Id };
+ var remove = new MenuFlyoutItem { Text = LocalizationHelper.GetString("ConnectionPage_Remove"), Tag = row.Id };
remove.Click += OnSavedRowRemove;
flyout.Items.Add(remove);
}
@@ -1414,12 +1418,12 @@ private static string FormatRelative(DateTime? when)
{
if (when == null) return "";
var span = DateTime.UtcNow - when.Value.ToUniversalTime();
- if (span.TotalMinutes < 1) return "just now";
- if (span.TotalMinutes < 60) return $"{(int)span.TotalMinutes}m ago";
- if (span.TotalHours < 24) return $"{(int)span.TotalHours}h ago";
- if (span.TotalDays < 30) return $"{(int)span.TotalDays}d ago";
- if (span.TotalDays < 365) return $"{(int)(span.TotalDays / 30)}mo ago";
- return $"{(int)(span.TotalDays / 365)}y ago";
+ if (span.TotalMinutes < 1) return LocalizationHelper.GetString("ConnectionPage_JustNow");
+ if (span.TotalMinutes < 60) return string.Format(LocalizationHelper.GetString("ConnectionPage_MinutesAgo"), (int)span.TotalMinutes);
+ if (span.TotalHours < 24) return string.Format(LocalizationHelper.GetString("ConnectionPage_HoursAgo"), (int)span.TotalHours);
+ if (span.TotalDays < 30) return string.Format(LocalizationHelper.GetString("ConnectionPage_DaysAgo"), (int)span.TotalDays);
+ if (span.TotalDays < 365) return string.Format(LocalizationHelper.GetString("ConnectionPage_MonthsAgo"), (int)(span.TotalDays / 30));
+ return string.Format(LocalizationHelper.GetString("ConnectionPage_YearsAgo"), (int)(span.TotalDays / 365));
}
private sealed class SavedGatewayRow
@@ -1486,7 +1490,7 @@ private void OnAddBack(object sender, RoutedEventArgs e)
AddResultText.Text = "";
AddSetupCodeBox.Text = "";
AddSetupCodePreviewPanel.Visibility = Visibility.Collapsed;
- AddScanStatusText.Text = "Press Start scan to look for gateways on your network.";
+ AddScanStatusText.Text = LocalizationHelper.GetString("ConnectionPage_PressScan");
AddScanProgressBar.Visibility = Visibility.Collapsed;
AddScanResultsPanel.Children.Clear();
RefreshFromSnapshot(_lastSnapshot);
@@ -1769,11 +1773,11 @@ private void OnRestartTunnel(object sender, RoutedEventArgs e)
{
var app = (App)Microsoft.UI.Xaml.Application.Current;
app.EnsureSshTunnelStarted();
- AddResultText.Text = "Tunnel restart triggered.";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_TunnelRestartTriggered");
}
catch (Exception ex)
{
- AddResultText.Text = $"Tunnel restart failed: {ex.Message}";
+ AddResultText.Text = string.Format(LocalizationHelper.GetString("ConnectionPage_TunnelRestartFailed"), ex.Message);
}
}
@@ -1817,8 +1821,8 @@ private async Task OnApplyRepairCodeAsync()
{
var result = await _connectionManager.ApplySetupCodeAsync(code);
AddResultText.Text = result.Outcome == SetupCodeOutcome.Success
- ? "Re-paired. Reconnecting…"
- : $"✗ {result.ErrorMessage ?? "Could not apply code."}";
+ ? LocalizationHelper.GetString("ConnectionPage_RepairedReconnecting")
+ : $"✗ {result.ErrorMessage ?? LocalizationHelper.GetString("ConnectionPage_CouldNotApplyCode")}";
}
catch (Exception ex)
{
@@ -1858,7 +1862,7 @@ private async Task OnConnectSavedGatewayAsync(object sender)
// gets feedback even if the snapshot is briefly silent.
try
{
- AuthErrorBar.Title = "Connect failed";
+ AuthErrorBar.Title = LocalizationHelper.GetString("ConnectionPage_ConnectFailed");
AuthErrorBar.Message = ex.Message;
AuthErrorBar.Severity = Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error;
AuthErrorBar.IsOpen = true;
@@ -1928,10 +1932,10 @@ private async Task OnSavedRowRemoveAsync(object sender)
if (rec == null) return;
var dialog = new ContentDialog
{
- Title = "Remove gateway?",
- Content = $"This will forget {rec.FriendlyName ?? rec.Url} and its credentials on this machine.",
- PrimaryButtonText = "Remove",
- CloseButtonText = "Cancel",
+ Title = LocalizationHelper.GetString("ConnectionPage_RemoveGatewayTitle"),
+ Content = string.Format(LocalizationHelper.GetString("ConnectionPage_RemoveGatewayMessage"), rec.FriendlyName ?? rec.Url),
+ PrimaryButtonText = LocalizationHelper.GetString("ConnectionPage_Remove"),
+ CloseButtonText = LocalizationHelper.GetString("ConnectionPage_CancelAction"),
DefaultButton = ContentDialogButton.Close,
XamlRoot = this.XamlRoot,
};
@@ -2011,7 +2015,7 @@ private async Task RunConnectivityTestAsync(string rawUrl, System.Threading.Canc
AddTestResultText.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
AddTestResultText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["TextFillColorSecondaryBrush"];
- AddTestResultText.Text = "Testing connection…";
+ AddTestResultText.Text = LocalizationHelper.GetString("ConnectionPage_TestingConnection");
try
{
@@ -2037,17 +2041,17 @@ private async Task RunConnectivityTestAsync(string rawUrl, System.Threading.Canc
if (response != null && response.IsSuccessStatusCode)
{
- AddTestResultText.Text = $"✓ Gateway reachable";
+ AddTestResultText.Text = $"✓ {LocalizationHelper.GetString("ConnectionPage_GatewayReachable")}";
AddTestResultText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["SystemFillColorSuccessBrush"];
}
else if (response != null)
{
- AddTestResultText.Text = $"⚠ Gateway responded with {(int)response.StatusCode}";
+ AddTestResultText.Text = $"⚠ {string.Format(LocalizationHelper.GetString("ConnectionPage_GatewayRespondedWith"), (int)response.StatusCode)}";
AddTestResultText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["SystemFillColorCautionBrush"];
}
else
{
- AddTestResultText.Text = $"✗ Cannot reach gateway";
+ AddTestResultText.Text = $"✗ {LocalizationHelper.GetString("ConnectionPage_CannotReachGateway")}";
AddTestResultText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["SystemFillColorCriticalBrush"];
}
}
@@ -2078,7 +2082,7 @@ private async Task OnAddSaveAsync()
{
case "direct": await DoDirectConnectFromAddFormAsync(); break;
case "setup": await DoApplySetupCodeFromAddFormAsync(); break;
- case "scan": AddResultText.Text = "Pick a discovered gateway above to connect."; break;
+ case "scan": AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_PickDiscoveredGateway"); break;
}
}
catch (Exception ex)
@@ -2103,7 +2107,7 @@ private async Task DoDirectConnectFromAddFormAsync()
var friendly = DirectNameBox.Text?.Trim();
if (string.IsNullOrWhiteSpace(url))
{
- AddResultText.Text = "Enter a gateway URL";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_EnterGatewayUrl");
return;
}
@@ -2120,19 +2124,19 @@ private async Task DoDirectConnectFromAddFormAsync()
var sshHost = AddSshHostBox.Text.Trim();
if (!int.TryParse(AddSshRemotePortBox.Text, out var remotePort) || remotePort is < 1 or > 65535)
{
- AddResultText.Text = "SSH remote port must be 1–65535";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_SshRemotePortInvalid");
return;
}
if (!int.TryParse(AddSshLocalPortBox.Text, out var localPort) || localPort is < 1 or > 65535)
{
- AddResultText.Text = "SSH local port must be 1–65535";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_SshLocalPortInvalid");
return;
}
sshConfig = new SshTunnelConfig(sshUser, sshHost, remotePort, localPort);
}
AddSaveButton.IsEnabled = false;
- AddResultText.Text = "Connecting…";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_Connecting");
// Snapshot previous state for rollback (mirrors legacy logic exactly)
var previousActiveId = _gatewayRegistry.ActiveGatewayId;
@@ -2235,15 +2239,15 @@ private async Task DoDirectConnectFromAddFormAsync()
if (useSsh)
{
- AddResultText.Text = "Starting SSH tunnel…";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_StartingSshTunnel");
var app = (App)Microsoft.UI.Xaml.Application.Current;
app.EnsureSshTunnelStarted();
}
var snapshot = await ConnectAndWaitForDirectConnectOutcomeAsync(recordId);
AddResultText.Text = snapshot.OperatorState == RoleConnectionState.PairingRequired
- ? $"Pairing approval required for {GatewayUrlHelper.SanitizeForDisplay(url)}."
- : $"Connected to {GatewayUrlHelper.SanitizeForDisplay(url)}.";
+ ? string.Format(LocalizationHelper.GetString("ConnectionPage_PairingApprovalRequired"), GatewayUrlHelper.SanitizeForDisplay(url))
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_ConnectedTo"), GatewayUrlHelper.SanitizeForDisplay(url));
// Success — leave Add mode and stop tracking the edited record.
_editingGatewayId = null;
@@ -2298,7 +2302,7 @@ void OnStateChanged(object? sender, GatewayConnectionSnapshot snapshot)
var completed = await Task.WhenAny(completion.Task, Task.Delay(TimeSpan.FromSeconds(15)));
if (completed != completion.Task)
- throw new TimeoutException("Timed out waiting for the gateway connection to complete.");
+ throw new TimeoutException(LocalizationHelper.GetString("ConnectionPage_ConnectionTimeout"));
return EnsureDirectConnectSucceeded(await completion.Task);
}
@@ -2319,7 +2323,7 @@ private static GatewayConnectionSnapshot EnsureDirectConnectSucceeded(GatewayCon
{
if (snapshot.OperatorState == RoleConnectionState.Error)
{
- var message = snapshot.OperatorError ?? snapshot.NodeError ?? "Gateway connection failed.";
+ var message = snapshot.OperatorError ?? snapshot.NodeError ?? LocalizationHelper.GetString("ConnectionPage_GatewayConnectionFailed");
throw new InvalidOperationException(message);
}
return snapshot;
@@ -2394,12 +2398,12 @@ private async Task DoApplySetupCodeFromAddFormAsync()
var code = AddSetupCodeBox.Text?.Trim();
if (string.IsNullOrEmpty(code))
{
- AddResultText.Text = "Please paste a setup code.";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_PleaseEnterSetupCode");
return;
}
AddSaveButton.IsEnabled = false;
- AddResultText.Text = "Applying…";
+ AddResultText.Text = LocalizationHelper.GetString("ConnectionPage_Applying");
try
{
if (_connectionManager != null)
@@ -2407,11 +2411,11 @@ private async Task DoApplySetupCodeFromAddFormAsync()
var result = await _connectionManager.ApplySetupCodeAsync(code);
AddResultText.Text = result.Outcome switch
{
- SetupCodeOutcome.Success => $"✓ Applied — gateway: {SanitizeUrl(result.GatewayUrl ?? "")}",
- SetupCodeOutcome.InvalidCode => $"✗ {result.ErrorMessage ?? "Invalid setup code"}",
- SetupCodeOutcome.InvalidUrl => $"✗ {result.ErrorMessage ?? "Invalid URL"}",
- SetupCodeOutcome.ConnectionFailed => $"✗ {result.ErrorMessage ?? "Connection failed"}",
- _ => $"✗ {result.ErrorMessage ?? "Unknown error"}",
+ SetupCodeOutcome.Success => $"✓ {string.Format(LocalizationHelper.GetString("ConnectionPage_AppliedGateway"), SanitizeUrl(result.GatewayUrl ?? ""))}",
+ SetupCodeOutcome.InvalidCode => $"✗ {result.ErrorMessage ?? LocalizationHelper.GetString("ConnectionPage_InvalidSetupCode")}",
+ SetupCodeOutcome.InvalidUrl => $"✗ {result.ErrorMessage ?? LocalizationHelper.GetString("ConnectionPage_InvalidUrl")}",
+ SetupCodeOutcome.ConnectionFailed => $"✗ {result.ErrorMessage ?? LocalizationHelper.GetString("ConnectionPage_ConnectionFailed")}",
+ _ => $"✗ {result.ErrorMessage ?? LocalizationHelper.GetString("ConnectionPage_UnknownError")}",
};
if (result.Outcome == SetupCodeOutcome.Success)
{
@@ -2434,7 +2438,7 @@ private async Task DoApplySetupCodeFromAddFormAsync()
if (!string.IsNullOrEmpty(decoded.Url))
settings.GatewayUrl = decoded.Url;
settings.Save();
- AddResultText.Text = $"✓ Applied — gateway: {SanitizeUrl(decoded.Url ?? settings.GatewayUrl ?? "")}";
+ AddResultText.Text = $"✓ {string.Format(LocalizationHelper.GetString("ConnectionPage_AppliedGateway"), SanitizeUrl(decoded.Url ?? settings.GatewayUrl ?? ""))}";
((IAppCommands)CurrentApp).NotifySettingsSaved();
}
}
@@ -2460,11 +2464,11 @@ private void OnSetupCodeTextChanged(object sender, TextChangedEventArgs? e)
var decoded = SetupCodeDecoder.Decode(code);
if (decoded.Success)
{
- AddSetupCodePreviewUrl.Text = $"Gateway: {decoded.Url ?? "(not specified)"}";
+ AddSetupCodePreviewUrl.Text = string.Format(LocalizationHelper.GetString("ConnectionPage_PreviewGateway"), decoded.Url ?? LocalizationHelper.GetString("ConnectionPage_PreviewGatewayNotSpecified"));
var tokenHint = string.IsNullOrEmpty(decoded.Token)
- ? "(no token)"
+ ? LocalizationHelper.GetString("ConnectionPage_PreviewNoToken")
: decoded.Token!.Substring(0, Math.Min(8, decoded.Token.Length)) + "…";
- AddSetupCodePreviewToken.Text = $"Token: {tokenHint}";
+ AddSetupCodePreviewToken.Text = string.Format(LocalizationHelper.GetString("ConnectionPage_PreviewToken"), tokenHint);
AddSetupCodePreviewPanel.Visibility = Visibility.Visible;
// Auto-test connectivity with the decoded URL
if (!string.IsNullOrEmpty(decoded.Url))
@@ -2524,10 +2528,10 @@ private async Task RunScanForGatewaysAsync()
_scanInProgress = true;
// Reflect "scanning" state on both buttons + sub-section
- GatewaysScanButtonText.Text = "Stop";
- WelcomeScanButtonText.Text = "Stop";
+ GatewaysScanButtonText.Text = LocalizationHelper.GetString("ConnectionPage_Stop");
+ WelcomeScanButtonText.Text = LocalizationHelper.GetString("ConnectionPage_Stop");
GatewaysDiscoveredSection.Visibility = Visibility.Visible;
- GatewaysDiscoveredHeader.Text = "Scanning your network…";
+ GatewaysDiscoveredHeader.Text = LocalizationHelper.GetString("ConnectionPage_ScanningNetwork");
GatewaysDiscoveredProgress.IsActive = true;
GatewaysDiscoveredProgress.Visibility = Visibility.Visible;
GatewaysDiscoveredList.Children.Clear();
@@ -2539,8 +2543,8 @@ private async Task RunScanForGatewaysAsync()
await _discoveryService.StartDiscoveryAsync();
var gateways = _discoveryService.Gateways;
GatewaysDiscoveredHeader.Text = gateways.Count == 0
- ? "No gateways found on your network."
- : $"Discovered on your network ({gateways.Count})";
+ ? LocalizationHelper.GetString("ConnectionPage_NoGatewaysFound")
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_DiscoveredOnNetwork"), gateways.Count);
GatewaysDiscoveredProgress.IsActive = false;
GatewaysDiscoveredProgress.Visibility = Visibility.Collapsed;
@@ -2552,15 +2556,15 @@ private async Task RunScanForGatewaysAsync()
}
catch (Exception ex)
{
- GatewaysDiscoveredHeader.Text = $"Scan failed: {ex.Message}";
+ GatewaysDiscoveredHeader.Text = string.Format(LocalizationHelper.GetString("ConnectionPage_ScanFailed"), ex.Message);
GatewaysDiscoveredProgress.IsActive = false;
GatewaysDiscoveredProgress.Visibility = Visibility.Collapsed;
}
finally
{
_scanInProgress = false;
- GatewaysScanButtonText.Text = "Scan";
- WelcomeScanButtonText.Text = "Scan";
+ GatewaysScanButtonText.Text = LocalizationHelper.GetString("ConnectionPage_ScanAction");
+ WelcomeScanButtonText.Text = LocalizationHelper.GetString("ConnectionPage_ScanAction");
}
}
@@ -2609,7 +2613,7 @@ private Border BuildDiscoveredRow(DiscoveredGateway gw)
Grid.SetColumn(info, 1);
grid.Children.Add(info);
- var addBtn = new Button { Content = "Add", Tag = gw.ConnectionUrl, VerticalAlignment = VerticalAlignment.Center };
+ var addBtn = new Button { Content = LocalizationHelper.GetString("ConnectionPage_Add"), Tag = gw.ConnectionUrl, VerticalAlignment = VerticalAlignment.Center };
addBtn.Click += OnAddDiscoveredGateway;
Grid.SetColumn(addBtn, 2);
grid.Children.Add(addBtn);
@@ -2844,8 +2848,8 @@ private void UpdatePendingApprovalsVisibility()
}
PendingApprovalsBanner.Visibility = Visibility.Visible;
PendingApprovalsHeaderText.Text = total == 1
- ? "1 approval waiting on you"
- : $"{total} approvals waiting on you";
+ ? LocalizationHelper.GetString("ConnectionPage_ApprovalsSingular")
+ : string.Format(LocalizationHelper.GetString("ConnectionPage_ApprovalsPlural"), total);
}
///
@@ -2968,10 +2972,10 @@ private Border BuildDevicePairingCard(DevicePairingRequest req, bool canPair)
var info = new StackPanel { Spacing = 2 };
info.Children.Add(new TextBlock
{
- Text = $"Device · {req.DisplayName ?? req.DeviceId}",
+ Text = string.Format(LocalizationHelper.GetString("ConnectionPage_DeviceLabel"), req.DisplayName ?? req.DeviceId),
Style = (Style)Application.Current.Resources["BodyStrongTextBlockStyle"],
});
- var detail = req.Platform ?? "unknown";
+ var detail = req.Platform ?? LocalizationHelper.GetString("ConnectionPage_UnknownPlatform");
if (!string.IsNullOrEmpty(req.Role)) detail += $" · {req.Role}";
info.Children.Add(new TextBlock
{
@@ -3003,14 +3007,14 @@ private Border BuildDevicePairingCard(DevicePairingRequest req, bool canPair)
var approveBtn = BuildPairingDecisionButton(
glyph: Helpers.FluentIconCatalog.Check,
glyphBrushKey: "SystemFillColorSuccessBrush",
- label: "Approve",
- automationName: "Approve pairing request",
+ label: LocalizationHelper.GetString("ConnectionPage_Approve"),
+ automationName: LocalizationHelper.GetString("ConnectionPage_ApprovePairingRequest"),
automationId: "DevicePairApproveAction");
var rejectBtn = BuildPairingDecisionButton(
glyph: Helpers.FluentIconCatalog.Exit,
glyphBrushKey: "SystemFillColorCriticalBrush",
- label: "Deny",
- automationName: "Deny pairing request",
+ label: LocalizationHelper.GetString("ConnectionPage_Deny"),
+ automationName: LocalizationHelper.GetString("ConnectionPage_DenyPairingRequest"),
automationId: "DevicePairDenyAction");
if (string.IsNullOrEmpty(capturedId))
{
@@ -3061,10 +3065,10 @@ private Border BuildNodePairingCard(PairingRequest req, bool canPair)
var info = new StackPanel { Spacing = 2 };
info.Children.Add(new TextBlock
{
- Text = $"Node · {req.DisplayName ?? req.NodeId}",
+ Text = string.Format(LocalizationHelper.GetString("ConnectionPage_NodeLabel"), req.DisplayName ?? req.NodeId),
Style = (Style)Application.Current.Resources["BodyStrongTextBlockStyle"],
});
- var detail = req.Platform ?? "unknown";
+ var detail = req.Platform ?? LocalizationHelper.GetString("ConnectionPage_UnknownPlatform");
if (!string.IsNullOrEmpty(req.RemoteIp)) detail += $" · {req.RemoteIp}";
info.Children.Add(new TextBlock
{
@@ -3076,7 +3080,7 @@ private Border BuildNodePairingCard(PairingRequest req, bool canPair)
{
info.Children.Add(new TextBlock
{
- Text = "⚠️ Repair request",
+ Text = LocalizationHelper.GetString("ConnectionPage_RepairRequest"),
Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"],
Foreground = ResolveBrush("SystemFillColorCautionBrush"),
});
@@ -3095,14 +3099,14 @@ private Border BuildNodePairingCard(PairingRequest req, bool canPair)
var approveBtn = BuildPairingDecisionButton(
glyph: Helpers.FluentIconCatalog.Check,
glyphBrushKey: "SystemFillColorSuccessBrush",
- label: "Approve",
- automationName: "Approve pairing request",
+ label: LocalizationHelper.GetString("ConnectionPage_Approve"),
+ automationName: LocalizationHelper.GetString("ConnectionPage_ApprovePairingRequest"),
automationId: "NodePairApproveAction");
var rejectBtn = BuildPairingDecisionButton(
glyph: Helpers.FluentIconCatalog.Exit,
glyphBrushKey: "SystemFillColorCriticalBrush",
- label: "Deny",
- automationName: "Deny pairing request",
+ label: LocalizationHelper.GetString("ConnectionPage_Deny"),
+ automationName: LocalizationHelper.GetString("ConnectionPage_DenyPairingRequest"),
automationId: "NodePairDenyAction");
if (string.IsNullOrEmpty(capturedId))
{
@@ -3147,14 +3151,14 @@ await RunPairingDecisionAsync(approveBtn, rejectBtn, isApprove: false, async cli
private static string GetAuthErrorGuidance(string error)
{
if (error.Contains("token", StringComparison.OrdinalIgnoreCase))
- return $"{error}\n\nPaste a new setup code from the Add Gateway flow.";
+ return string.Format(LocalizationHelper.GetString("ConnectionPage_AuthGuidanceToken"), error);
if (error.Contains("pairing", StringComparison.OrdinalIgnoreCase))
- return $"{error}\n\nYour device needs approval on the gateway host.";
+ return string.Format(LocalizationHelper.GetString("ConnectionPage_AuthGuidancePairing"), error);
if (error.Contains("password", StringComparison.OrdinalIgnoreCase))
- return $"{error}\n\nThis gateway requires password authentication.";
+ return string.Format(LocalizationHelper.GetString("ConnectionPage_AuthGuidancePassword"), error);
if (error.Contains("signature", StringComparison.OrdinalIgnoreCase))
- return $"{error}\n\nThe gateway may require a different auth protocol version.";
- return $"{error}\n\nCheck your connection settings and try again.";
+ return string.Format(LocalizationHelper.GetString("ConnectionPage_AuthGuidanceSignature"), error);
+ return string.Format(LocalizationHelper.GetString("ConnectionPage_AuthGuidanceDefault"), error);
}
private static string SanitizeUrl(string url)
diff --git a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml
index 1e422fe2..dbb45360 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml
+++ b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml
@@ -1,4 +1,4 @@
-
+
-
+
-
+
-
+
@@ -64,13 +64,13 @@
-
-
+
@@ -81,7 +81,7 @@
BorderThickness="1"
CornerRadius="8" Padding="20" Margin="0,0,0,12">
-
+
@@ -90,34 +90,34 @@
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -127,22 +127,22 @@
-
+
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -156,16 +156,16 @@
-
+
-
+
-
-
-
+
+
+
@@ -173,21 +173,21 @@
-
+
-
-
-
-
-
+
@@ -198,23 +198,23 @@
-
+
-
-
+
+
-
-
+
+
-
@@ -226,17 +226,17 @@
-
+
-
-
+
+
-
+
-
-
+
+
@@ -251,8 +251,8 @@
-
-
+
@@ -268,7 +268,7 @@
HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="Visible">
-
@@ -281,10 +281,10 @@
-
-
@@ -293,4 +293,3 @@
-
diff --git a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs
index 2928e827..1c756d32 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs
@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using OpenClaw.Shared;
+using OpenClawTray.Helpers;
using OpenClawTray.Services;
using System;
using System.Collections.Generic;
@@ -128,7 +129,7 @@ private void OnRunNowClick(object sender, RoutedEventArgs e)
var vm = _jobs.Find(j => j.Id == jobId);
if (vm != null && !vm.IsEnabled) return;
_runningJobIds.Add(jobId);
- btn!.Content = "Running...";
+ btn!.Content = LocalizationHelper.GetString("CronPage_Running");
btn.IsEnabled = false;
CurrentApp.GatewayClient.RunCronJobAsync(jobId).ContinueWith(t =>
@@ -196,8 +197,8 @@ private void OnNewJobClick(object sender, RoutedEventArgs e)
_editingJobId = null;
RestoreFormFromInline(); // ensure form is back in its home position
ResetForm();
- FormTitle.Text = "New Job";
- FormSaveButton.Content = "Create Job";
+ FormTitle.Text = LocalizationHelper.GetString("CronPage_NewJobTitle");
+ FormSaveButton.Content = LocalizationHelper.GetString("CronPage_CreateJobLabel");
JobFormPanel.Visibility = Visibility.Visible;
}
@@ -211,8 +212,8 @@ private void OnEditJobClick(object sender, RoutedEventArgs e)
if (vm == null || !vm.IsEnabled) return;
_editingJobId = jobId;
- FormTitle.Text = "Edit Job";
- FormSaveButton.Content = "Save Changes";
+ FormTitle.Text = LocalizationHelper.GetString("CronPage_EditJob");
+ FormSaveButton.Content = LocalizationHelper.GetString("CronPage_SaveChanges");
// Populate form fields from VM
FormName.Text = vm.Name;
diff --git a/src/OpenClaw.Tray.WinUI/Pages/SandboxPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/SandboxPage.xaml
index 4a0f3aee..7a5fc4e3 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/SandboxPage.xaml
+++ b/src/OpenClaw.Tray.WinUI/Pages/SandboxPage.xaml
@@ -108,7 +108,7 @@
Foreground="{ThemeResource SystemFillColorCautionBrush}"
VerticalAlignment="Top"
Margin="0,2,0,0" />
-
diff --git a/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml
index 7aa4124b..456d82b6 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml
+++ b/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml
@@ -31,7 +31,7 @@
Text="Sessions"
Style="{StaticResource TitleTextBlockStyle}"
Margin="0,0,0,2"/>
-
@@ -100,7 +100,7 @@
Text="No active sessions"
Style="{StaticResource BodyStrongTextBlockStyle}"
HorizontalAlignment="Center"/>
-
-
@@ -39,7 +39,7 @@
HorizontalContentAlignment="Stretch"
Visibility="Collapsed">
-
diff --git a/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml
index c38639ec..892b5715 100644
--- a/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml
+++ b/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml
@@ -193,7 +193,7 @@
Width="100" Visibility="Collapsed">
-
+
@@ -216,7 +216,7 @@
Width="140">
-
+
diff --git a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
index 8f22d472..7740514b 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
@@ -3954,13 +3954,13 @@ On your gateway host (Mac/Linux), run:
SCHEDULE
-
+
Every
-
+
At
-
+
Cron
@@ -4625,4 +4625,906 @@ On your gateway host (Mac/Linux), run:
Advanced
-
+
+
+ Live
+
+
+ Clear
+
+
+ Conversations this gateway is currently handling, grouped by channel.
+
+
+ Sessions appear here when a channel is connected and traffic is flowing.
+
+
+ Open chat
+
+
+ Enabled
+
+
+ Disabled
+
+
+ Preview
+
+
+ Preview Voice
+
+
+ Disconnected
+
+
+ Commands the agent runs directly on the gateway aren't. If the gateway is on this PC they may still be able to reach your Windows files, clipboard, and network — check your gateway's configuration.
+
+ All Agents
+
+
+ {0} of {1} events
+
+
+ Create Job
+
+
+ Edit Job
+
+
+ New Job
+
+
+ Running...
+
+
+ Save Changes
+
+
+ Add item
+
+
+ Attach file
+
+
+ Cancel
+
+
+ Cannot attach file
+
+
+ Cannot connect to gateway at {0}
+
+Make sure the gateway is running.
+
+
+ Chat failed to start:
+{0}
+
+
+ Chat is ready; starting your first hatching conversation…
+
+
+ The gateway is connected; the chat surface is still coming online.
+
+
+ Gateway pairing is not complete. Open Connection settings to finish pairing.
+
+
+ Invalid gateway URL: {0}
+
+
+ OK
+
+
+ Open Connection settings to finish pairing with a gateway.
+
+
+ Open Voice Settings
+
+
+ Timed out waiting for chat at {0}. Retry once the gateway is ready.
+
+
+ Timed out waiting for the gateway operator handshake. Retry once the gateway is ready.
+
+
+ The speech-to-text model needs to be installed before you can use voice input. Would you like to open Voice settings to install it?
+
+
+ Voice Model Required
+
+
+ WebView2 failed to initialize:
+{0}
+
+
+ Applying…
+
+
+ Awaiting approval
+
+
+ 🔐 Awaiting approval — approve then click Connect
+
+
+ 🔐 Awaiting approval from gateway
+
+
+ Connect
+
+
+ ✓ Connected
+
+
+ ✓ Connected to {0}
+
+
+ Connecting…
+
+
+ Connect (once approved)
+
+
+ disabled
+
+
+ Disconnected
+
+
+ Enter a gateway URL
+
+
+ No gateways
+
+
+ Reconnecting…
+
+
+ rejected
+
+
+ Starting SSH tunnel…
+
+
+ on {0}
+
+
+ 1 client
+
+
+ {0} clients
+
+
+ {0}/{1} channel
+
+
+ {0}/{1} channels
+
+
+ ${0} today
+
+
+ $0.00 today
+
+
+ Shared token
+
+
+ Tap to copy shared token
+
+
+ via SSH tunnel
+
+
+ Local
+
+
+ Tailscale
+
+
+ LAN
+
+
+ Remote
+
+
+ Active · {0} sessions
+
+
+ Active · 1 session
+
+
+ Active · no current sessions
+
+
+ Disconnected
+
+
+ Connecting…
+
+
+ Reconnecting…
+
+
+ Node active · {0} capabilities
+
+
+ Node active · 1 capability
+
+
+ Node active · no capabilities enabled
+
+
+ Awaiting pairing approval
+
+
+ Pairing rejected
+
+
+ Rate-limited
+
+
+ Node error
+
+
+ Node mode disabled
+
+
+ No capabilities enabled. Pick what to share in Permissions.
+
+
+ Awaiting approval on the gateway host.
+
+
+ Pairing was rejected. Re-pair from Add Gateway.
+
+
+ Rate-limited by the gateway. Will retry after cooldown.
+
+
+ Node reported an error.
+
+
+ Providing no capabilities
+
+
+ Providing 1 capability: {0}
+
+
+ Providing {0} capabilities: {1}
+
+
+ Connected
+
+
+ Connecting…
+
+
+ Awaiting approval
+
+
+ Disconnecting…
+
+
+ Connect
+
+
+ Open dashboard
+
+
+ Disconnect
+
+
+ Edit
+
+
+ Remove
+
+
+ via SSH
+
+
+ Saved gateways (1)
+
+
+ Saved gateways ({0})
+
+
+ just now
+
+
+ {0}m ago
+
+
+ {0}h ago
+
+
+ {0}d ago
+
+
+ {0}mo ago
+
+
+ {0}y ago
+
+
+ Re-pair this gateway:
+
+
+ Awaiting approval:
+
+
+ Help us fix the SSH tunnel:
+
+
+ Help us fix this connection:
+
+
+ Get a fresh setup code from the gateway host.
+
+
+ Or paste a new direct token below.
+
+
+ Approve this client on the gateway host using the command below.
+
+
+ Once approved, click Connect to resume.
+
+
+ Confirm SSH is reachable on the host.
+
+
+ Restart the tunnel below if it stopped.
+
+
+ Check the gateway logs on the host.
+
+
+ If the gateway is partially up, open its dashboard.
+
+
+ Edit gateway settings to change URL or token.
+
+
+ Check that the gateway is running on the host.
+
+
+ Check that you're on the same network or VPN.
+
+
+ If using SSH tunnel: confirm it's up.
+
+
+ SSH tunnel is down.
+
+
+ Testing connection…
+
+
+ Gateway reachable
+
+
+ Gateway responded with {0}
+
+
+ Cannot reach gateway
+
+
+ Enter a gateway URL
+
+
+ Connecting…
+
+
+ Starting SSH tunnel…
+
+
+ Pairing approval required for {0}.
+
+
+ Connected to {0}.
+
+
+ SSH remote port must be 1–65535
+
+
+ SSH local port must be 1–65535
+
+
+ Tunnel restart triggered.
+
+
+ Tunnel restart failed: {0}
+
+
+ Re-paired. Reconnecting…
+
+
+ Could not apply code.
+
+
+ Please paste a setup code.
+
+
+ Applying…
+
+
+ Applied — gateway: {0}
+
+
+ Invalid setup code
+
+
+ Invalid URL
+
+
+ Connection failed
+
+
+ Unknown error
+
+
+ Gateway: {0}
+
+
+ (not specified)
+
+
+ (no token)
+
+
+ Token: {0}
+
+
+ Press Start scan to look for gateways on your network.
+
+
+ Scanning your network…
+
+
+ No gateways found on your network.
+
+
+ Discovered on your network ({0})
+
+
+ Scan failed: {0}
+
+
+ Stop
+
+
+ Scan
+
+
+ Add
+
+
+ Pick a discovered gateway above to connect.
+
+
+ Remove gateway?
+
+
+ This will forget {0} and its credentials on this machine.
+
+
+ Cancel
+
+
+ Connect failed
+
+
+ Gateway connection failed.
+
+
+ 1 approval waiting on you
+
+
+ {0} approvals waiting on you
+
+
+ Device · {0}
+
+
+ Node · {0}
+
+
+ ⚠️ Repair request
+
+
+ Approve
+
+
+ Deny
+
+
+ Approve pairing request
+
+
+ Deny pairing request
+
+
+ {0}
+
+Paste a new setup code from the Add Gateway flow.
+
+
+ {0}
+
+Your device needs approval on the gateway host.
+
+
+ {0}
+
+This gateway requires password authentication.
+
+
+ {0}
+
+The gateway may require a different auth protocol version.
+
+
+ {0}
+
+Check your connection settings and try again.
+
+
+ bootstrap
+
+
+ shared token
+
+
+ device token
+
+
+ unknown
+
+
+ Timed out waiting for the gateway connection to complete.
+
+
+ This channel requires a macOS host. It can’t be configured from a Windows machine.
+
+
+ Replace credentials
+
+
+ Configuration
+
+
+ Unlink this device from the channel. Scan a fresh QR to reconnect — your account stays paired on your phone.
+
+
+ Pause the channel temporarily — credentials are kept so you can start it again. Or disconnect entirely to clear them.
+
+
+ Start the channel — it’ll begin handling messages with the stored credentials. Or disconnect entirely to clear them.
+
+
+ Pause the channel — credentials are kept so you can start it again.
+
+
+ Start the channel — it’ll begin handling messages with the stored credentials.
+
+
+ Start
+
+
+ Stop
+
+
+ Logout
+
+
+ Disconnect and forget credentials
+
+
+ Controls
+
+
+ WhatsApp Linked devices help →
+
+
+ Signal Linked devices help →
+
+
+ How to create a Telegram bot →
+
+
+ How to create a Discord webhook →
+
+
+ How to add a Google Chat webhook →
+
+
+ Slack app dashboard →
+
+
+ About Nostr →
+
+
+ About macOS Messages →
+
+
+ Link your WhatsApp phone
+
+
+ Click "Show QR" in the Linking section below.
+
+
+ On your phone: WhatsApp → Settings → Linked devices → Link a device.
+
+
+ Scan the QR code that appears here.
+
+
+ Link your Signal phone
+
+
+ Click "Show QR" in the Linking section below.
+
+
+ On your phone: Signal → Settings → Linked devices → Link new device.
+
+
+ Scan the QR code that appears here.
+
+
+ Connect Telegram via a bot
+
+
+ Open Telegram and send a message to @BotFather.
+
+
+ Send /newbot and follow the prompts. Copy the bot token at the end.
+
+
+ Paste the token into the Configuration form below.
+
+
+ Click "Save and start". The channel will start automatically.
+
+
+ Connect Discord via a webhook
+
+
+ Open your Discord server settings → Integrations → Webhooks.
+
+
+ Click "New Webhook", give it a name, and copy the webhook URL.
+
+
+ Paste the URL into the Configuration form below.
+
+
+ Click "Save and start".
+
+
+ Connect Google Chat via a webhook
+
+
+ In Google Chat, open the space → Manage webhooks → Add webhook.
+
+
+ Copy the webhook URL.
+
+
+ Paste the URL into the Configuration form below.
+
+
+ Click "Save and start".
+
+
+ Connect Slack via an app
+
+
+ Create a Slack app at api.slack.com/apps and install it to your workspace.
+
+
+ Copy the bot token (xoxb-…) and the signing secret.
+
+
+ Paste both into the Configuration form below.
+
+
+ Click "Save and start".
+
+
+ Connect Nostr via relays
+
+
+ Generate or paste a private key (nsec).
+
+
+ Pick one or more relay URLs (e.g. wss://relay.damus.io).
+
+
+ Paste both into the Configuration form below.
+
+
+ Click "Save and start".
+
+
+ iMessage is macOS-only
+
+
+ iMessage reads the local Messages.app database, which only exists on macOS.
+
+
+ To use iMessage with OpenClaw, run the gateway on a Mac instead of this Windows host.
+
+
+ All other channels in this list work fine from a Windows-hosted gateway.
+
+
+ Connect this channel
+
+
+ "{0}" is a plugin channel. Refer to its documentation for the fields it needs.
+
+
+ Use "Open Config page" in the Configuration section below to add settings under channels.{0}.
+
+
+ Save the config; OpenClaw will start the channel automatically.
+
+
+ Bot token
+
+
+ Webhook URL
+
+
+ Signing secret
+
+
+ Private key (nsec)
+
+
+ Relay URLs
+
+
+ Get from @BotFather (/newbot).
+
+
+ Server Settings → Integrations → Webhooks → New Webhook.
+
+
+ Open a space → Manage webhooks → Add webhook.
+
+
+ OAuth tokens from your Slack app.
+
+
+ Basic Information → App Credentials.
+
+
+ One per line.
+
+
+ Save changes
+
+
+ Save and start
+
+
+ Open Config page
+
+
+ Edit this channel's settings in the gateway Config page.
+
+
+ After following the steps above, save the config to start the channel.
+
+
+ Not connected
+
+
+ Connect to a gateway before saving channel config.
+
+
+ Missing field
+
+
+ {0} is required.
+
+
+ Saving {0}…
+
+
+ Writing {0} field(s) and enabling the channel.
+
+
+ Couldn’t load gateway config
+
+
+ The gateway didn’t return its current config — can’t safely save without it. Try Refresh and save again.
+
+
+ The gateway’s config was cleared mid-save. Try Refresh and save again.
+
+
+ Save blocked for {0}
+
+
+ The gateway didn’t respond.
+
+
+ Save failed for {0}
+
+
+ Your gateway config changed elsewhere (e.g., from the Config page). We refreshed the cache — try Save again. Details: {0}
+
+
+ {0} If this looks like a wire-format mismatch, open the Config page for direct JSON editing.
+
+
+ {0} config saved
+
+
+ Waiting for the gateway to confirm the channel is running…
+
+
+ WSL gateway
+
+
+ Open a shell or manage the local gateway service in WSL.
+
+
+ Terminal
+
+
+ Start
+
+
+ Stop
+
+
+ Restart
+
+
+ Open terminal
+
+
+ Open terminal
+
+
+ Open dashboard (in browser)
+
+
+ Open dashboard
+
+
+ Disconnect / reconnect
+
+
+ Disconnect or reconnect
+
+
+ Open terminal
+
+
+ Open terminal
+
+
+ Run openclaw gateway start in WSL
+
+
+ Start WSL gateway
+
+
+ Run openclaw gateway stop in WSL
+
+
+ Stop WSL gateway
+
+
+ Run openclaw gateway restart in WSL
+
+
+ Restart WSL gateway
+
+
diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
index 10d0ab86..94d9d813 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
@@ -2284,7 +2284,7 @@ Sur votre hôte passerelle (Mac/Linux), exécutez :
Utilisations
- Cron
+ Planification
Cet ordinateur
@@ -3906,13 +3906,13 @@ Sur votre hôte passerelle (Mac/Linux), exécutez :
PLANIFICATION
-
+
Toutes les
-
+
À
-
+
Cron
@@ -4577,4 +4577,906 @@ Sur votre hôte passerelle (Mac/Linux), exécutez :
Avancé
-
+
+
+ En direct
+
+
+ Effacer
+
+
+ Conversations actuellement gérées par cette passerelle, regroupées par canal.
+
+
+ Les sessions apparaissent ici lorsqu'un canal est connecté et que le trafic circule.
+
+
+ Ouvrir le chat
+
+
+ Activées
+
+
+ Désactivées
+
+
+ Aperçu
+
+
+ Aperçu de la voix
+
+
+ Déconnecté
+
+
+ Les commandes que l'agent exécute directement sur la passerelle ne le sont pas. Si la passerelle se trouve sur ce PC, elles peuvent toujours accéder à vos fichiers Windows, au presse-papiers et au réseau — vérifiez la configuration de votre passerelle.
+
+ Tous les agents
+
+
+ {0} sur {1} événements
+
+
+ Créer la tâche
+
+
+ Modifier la tâche
+
+
+ Nouvelle tâche
+
+
+ Exécution...
+
+
+ Enregistrer les modifications
+
+
+ Ajouter un élément
+
+
+ Joindre un fichier
+
+
+ Annuler
+
+
+ Impossible de joindre le fichier
+
+
+ Impossible de se connecter à la passerelle à {0}
+
+Assurez-vous que la passerelle est en cours d'exécution.
+
+
+ Échec du démarrage du chat :
+{0}
+
+
+ Le chat est prêt ; démarrage de votre première conversation…
+
+
+ La passerelle est connectée ; l'interface de chat est en cours de chargement.
+
+
+ Le jumelage avec la passerelle n'est pas terminé. Ouvrez les paramètres de connexion pour terminer le jumelage.
+
+
+ URL de passerelle non valide : {0}
+
+
+ OK
+
+
+ Ouvrez les paramètres de connexion pour terminer le jumelage avec une passerelle.
+
+
+ Ouvrir les paramètres vocaux
+
+
+ Délai d'attente dépassé pour le chat à {0}. Réessayez lorsque la passerelle est prête.
+
+
+ Délai d'attente dépassé pour la négociation avec l'opérateur de la passerelle. Réessayez lorsque la passerelle est prête.
+
+
+ Le modèle de reconnaissance vocale doit être installé avant de pouvoir utiliser la saisie vocale. Souhaitez-vous ouvrir les paramètres vocaux pour l'installer ?
+
+
+ Modèle vocal requis
+
+
+ Échec de l'initialisation de WebView2 :
+{0}
+
+
+ Application en cours…
+
+
+ En attente d'approbation
+
+
+ 🔐 En attente d'approbation — approuvez puis cliquez sur Connecter
+
+
+ 🔐 En attente d'approbation de la passerelle
+
+
+ Connecter
+
+
+ ✓ Connecté
+
+
+ ✓ Connecté à {0}
+
+
+ Connexion en cours…
+
+
+ Connecter (une fois approuvé)
+
+
+ désactivé
+
+
+ Déconnecté
+
+
+ Entrez une URL de passerelle
+
+
+ Aucune passerelle
+
+
+ Reconnexion en cours…
+
+
+ rejeté
+
+
+ Démarrage du tunnel SSH…
+
+
+ sur {0}
+
+
+ 1 client connecté
+
+
+ {0} clients connectés
+
+
+ {0}/{1} canal
+
+
+ {0}/{1} canaux
+
+
+ {0} $ aujourd’hui
+
+
+ 0,00 $ aujourd’hui
+
+
+ Jeton partagé
+
+
+ Appuyez pour copier le jeton partagé
+
+
+ via tunnel SSH
+
+
+ Locale
+
+
+ Tailscale
+
+
+ Réseau local
+
+
+ Distant
+
+
+ Actif · {0} sessions
+
+
+ Actif · 1 session
+
+
+ Actif · aucune session en cours
+
+
+ Déconnecté
+
+
+ Connexion en cours…
+
+
+ Reconnexion en cours…
+
+
+ Nœud actif · {0} capacités
+
+
+ Nœud actif · 1 capacité
+
+
+ Nœud actif · aucune capacité activée
+
+
+ En attente d’approbation de l’appairage
+
+
+ Appairage refusé
+
+
+ Limité en débit
+
+
+ Erreur du nœud
+
+
+ Mode nœud désactivé
+
+
+ Aucune capacité activée. Choisissez ce que vous souhaitez partager dans les Autorisations.
+
+
+ En attente d’approbation sur l’hôte de la passerelle.
+
+
+ L’appairage a été refusé. Réappairez depuis Ajouter une passerelle.
+
+
+ Limité en débit par la passerelle. Nouvel essai après refroidissement.
+
+
+ Le nœud a signalé une erreur.
+
+
+ Aucune capacité fournie
+
+
+ 1 capacité fournie : {0}
+
+
+ {0} capacités fournies : {1}
+
+
+ Connecté
+
+
+ Connexion en cours…
+
+
+ En attente d’approbation
+
+
+ Déconnexion en cours…
+
+
+ Connecter
+
+
+ Ouvrir le tableau de bord
+
+
+ Déconnecter
+
+
+ Modifier
+
+
+ Supprimer
+
+
+ via SSH
+
+
+ Passerelles enregistrées (1)
+
+
+ Passerelles enregistrées ({0})
+
+
+ à l’instant
+
+
+ il y a {0} min
+
+
+ il y a {0} h
+
+
+ il y a {0} j
+
+
+ il y a {0} mois
+
+
+ il y a {0} an(s)
+
+
+ Réappairer cette passerelle :
+
+
+ En attente d’approbation :
+
+
+ Aidez-nous à réparer le tunnel SSH :
+
+
+ Aidez-nous à réparer cette connexion :
+
+
+ Obtenez un nouveau code de configuration auprès de l’hôte de la passerelle.
+
+
+ Ou collez un nouveau jeton direct ci-dessous.
+
+
+ Approuvez ce client sur l’hôte de la passerelle à l’aide de la commande ci-dessous.
+
+
+ Une fois approuvé, cliquez sur Connecter pour reprendre.
+
+
+ Confirmez que SSH est accessible sur l’hôte.
+
+
+ Redémarrez le tunnel ci-dessous s’il s’est arrêté.
+
+
+ Vérifiez les journaux de la passerelle sur l’hôte.
+
+
+ Si la passerelle est partiellement en marche, ouvrez son tableau de bord.
+
+
+ Modifiez les paramètres de la passerelle pour changer l’URL ou le jeton.
+
+
+ Vérifiez que la passerelle est en cours d’exécution sur l’hôte.
+
+
+ Vérifiez que vous êtes sur le même réseau ou VPN.
+
+
+ Si vous utilisez un tunnel SSH : confirmez qu’il est actif.
+
+
+ Le tunnel SSH est interrompu.
+
+
+ Test de connexion…
+
+
+ Passerelle accessible
+
+
+ La passerelle a répondu avec {0}
+
+
+ Impossible d’atteindre la passerelle
+
+
+ Saisissez l’URL de la passerelle
+
+
+ Connexion en cours…
+
+
+ Démarrage du tunnel SSH…
+
+
+ Approbation d’appairage requise pour {0}.
+
+
+ Connecté à {0}.
+
+
+ Le port distant SSH doit être compris entre 1 et 65535
+
+
+ Le port local SSH doit être compris entre 1 et 65535
+
+
+ Redémarrage du tunnel déclenché.
+
+
+ Échec du redémarrage du tunnel : {0}
+
+
+ Réappairé. Reconnexion en cours…
+
+
+ Impossible d’appliquer le code.
+
+
+ Veuillez coller un code de configuration.
+
+
+ Application en cours…
+
+
+ Appliqué — passerelle : {0}
+
+
+ Code de configuration invalide
+
+
+ URL invalide
+
+
+ Échec de la connexion
+
+
+ Erreur inconnue
+
+
+ Passerelle : {0}
+
+
+ (non spécifié)
+
+
+ (pas de jeton)
+
+
+ Jeton : {0}
+
+
+ Appuyez sur Lancer l’analyse pour rechercher des passerelles sur votre réseau.
+
+
+ Analyse de votre réseau…
+
+
+ Aucune passerelle trouvée sur votre réseau.
+
+
+ Découverts sur votre réseau ({0})
+
+
+ Échec de l’analyse : {0}
+
+
+ Arrêter
+
+
+ Analyser
+
+
+ Ajouter
+
+
+ Choisissez une passerelle découverte ci-dessus pour vous connecter.
+
+
+ Supprimer la passerelle ?
+
+
+ Cela supprimera {0} et ses identifiants sur cette machine.
+
+
+ Annuler
+
+
+ Échec de la connexion
+
+
+ Échec de la connexion à la passerelle.
+
+
+ 1 approbation en attente
+
+
+ {0} approbations en attente
+
+
+ Appareil · {0}
+
+
+ Nœud · {0}
+
+
+ ⚠️ Demande de réparation
+
+
+ Approuver
+
+
+ Refuser
+
+
+ Approuver la demande d’appairage
+
+
+ Refuser la demande d’appairage
+
+
+ {0}
+
+Collez un nouveau code de configuration depuis le flux Ajouter une passerelle.
+
+
+ {0}
+
+Votre appareil nécessite une approbation sur l’hôte de la passerelle.
+
+
+ {0}
+
+Cette passerelle nécessite une authentification par mot de passe.
+
+
+ {0}
+
+La passerelle pourrait nécessiter une version différente du protocole d’authentification.
+
+
+ {0}
+
+Vérifiez vos paramètres de connexion et réessayez.
+
+
+ amorçage
+
+
+ jeton partagé
+
+
+ jeton d’appareil
+
+
+ inconnu
+
+
+ Le délai d’attente de la connexion à la passerelle a expiré.
+
+
+ Ce canal nécessite un hôte macOS. Il ne peut pas être configuré depuis une machine Windows.
+
+
+ Remplacer les identifiants
+
+
+ Paramètres
+
+
+ Dissocier cet appareil du canal. Scannez un nouveau QR pour vous reconnecter — votre compte reste associé sur votre téléphone.
+
+
+ Mettre le canal en pause temporairement — les identifiants sont conservés pour pouvoir le redémarrer. Ou déconnectez-vous entièrement pour les effacer.
+
+
+ Démarrer le canal — il commencera à traiter les messages avec les identifiants enregistrés. Ou déconnectez-vous entièrement pour les effacer.
+
+
+ Mettre le canal en pause — les identifiants sont conservés pour pouvoir le redémarrer.
+
+
+ Démarrer le canal — il commencera à traiter les messages avec les identifiants enregistrés.
+
+
+ Démarrer
+
+
+ Arrêter
+
+
+ Déconnexion
+
+
+ Déconnecter et oublier les identifiants
+
+
+ Commandes
+
+
+ Aide sur les appareils liés WhatsApp →
+
+
+ Aide sur les appareils liés Signal →
+
+
+ Comment créer un bot Telegram →
+
+
+ Comment créer un webhook Discord →
+
+
+ Comment ajouter un webhook Google Chat →
+
+
+ Tableau de bord des applications Slack →
+
+
+ À propos de Nostr →
+
+
+ À propos de Messages sur macOS →
+
+
+ Associer votre téléphone WhatsApp
+
+
+ Cliquez sur "Show QR" dans la section Liaison ci-dessous.
+
+
+ Sur votre téléphone : WhatsApp → Paramètres → Appareils liés → Lier un appareil.
+
+
+ Scannez le code QR qui s’affiche ici.
+
+
+ Associer votre téléphone Signal
+
+
+ Cliquez sur "Show QR" dans la section Liaison ci-dessous.
+
+
+ Sur votre téléphone : Signal → Paramètres → Appareils liés → Lier un nouvel appareil.
+
+
+ Scannez le code QR qui s’affiche ici.
+
+
+ Connecter Telegram via un bot
+
+
+ Ouvrez Telegram et envoyez un message à @BotFather.
+
+
+ Envoyez /newbot et suivez les instructions. Copiez le jeton du bot à la fin.
+
+
+ Collez le jeton dans le formulaire de configuration ci-dessous.
+
+
+ Cliquez sur "Save and start". Le canal démarrera automatiquement.
+
+
+ Connecter Discord via un webhook
+
+
+ Ouvrez les paramètres de votre serveur Discord → Intégrations → Webhooks.
+
+
+ Cliquez sur "New Webhook", donnez-lui un nom et copiez l’URL du webhook.
+
+
+ Collez l’URL dans le formulaire de configuration ci-dessous.
+
+
+ Cliquez sur "Save and start".
+
+
+ Connecter Google Chat via un webhook
+
+
+ Dans Google Chat, ouvrez l’espace → Gérer les webhooks → Ajouter un webhook.
+
+
+ Copiez l’URL du webhook.
+
+
+ Collez l’URL dans le formulaire de configuration ci-dessous.
+
+
+ Cliquez sur "Save and start".
+
+
+ Connecter Slack via une application
+
+
+ Créez une application Slack sur api.slack.com/apps et installez-la dans votre espace de travail.
+
+
+ Copiez le jeton du bot (xoxb-…) et le secret de signature.
+
+
+ Collez les deux dans le formulaire de configuration ci-dessous.
+
+
+ Cliquez sur "Save and start".
+
+
+ Connecter Nostr via des relais
+
+
+ Générez ou collez une clé privée (nsec).
+
+
+ Choisissez une ou plusieurs URL de relais (p. ex. wss://relay.damus.io).
+
+
+ Collez les deux dans le formulaire de configuration ci-dessous.
+
+
+ Cliquez sur "Save and start".
+
+
+ iMessage est réservé à macOS
+
+
+ iMessage lit la base de données locale de Messages.app, qui n’existe que sur macOS.
+
+
+ Pour utiliser iMessage avec OpenClaw, exécutez la passerelle sur un Mac au lieu de cet hôte Windows.
+
+
+ Tous les autres canaux de cette liste fonctionnent correctement depuis une passerelle hébergée sous Windows.
+
+
+ Connecter ce canal
+
+
+ "{0}" est un canal de plugin. Consultez sa documentation pour connaître les champs requis.
+
+
+ Utilisez "Open Config page" dans la section Configuration ci-dessous pour ajouter des paramètres sous channels.{0}.
+
+
+ Enregistrez la configuration ; OpenClaw démarrera le canal automatiquement.
+
+
+ Jeton du bot
+
+
+ URL du webhook
+
+
+ Secret de signature
+
+
+ Clé privée (nsec)
+
+
+ URL des relais
+
+
+ Obtenez-le auprès de @BotFather (/newbot).
+
+
+ Paramètres du serveur → Intégrations → Webhooks → New Webhook.
+
+
+ Ouvrez un espace → Gérer les webhooks → Ajouter un webhook.
+
+
+ Jetons OAuth de votre application Slack.
+
+
+ Informations de base → Identifiants de l'application.
+
+
+ Une par ligne.
+
+
+ Enregistrer les modifications
+
+
+ Enregistrer et démarrer
+
+
+ Ouvrir la page de configuration
+
+
+ Modifiez les paramètres de ce canal dans la page de configuration de la passerelle.
+
+
+ Après avoir suivi les étapes ci-dessus, enregistrez la configuration pour démarrer le canal.
+
+
+ Non connecté
+
+
+ Connectez-vous à une passerelle avant d’enregistrer la configuration du canal.
+
+
+ Champ manquant
+
+
+ {0} est requis.
+
+
+ Enregistrement de {0}…
+
+
+ Écriture de {0} champ(s) et activation du canal.
+
+
+ Impossible de charger la configuration de la passerelle
+
+
+ La passerelle n’a pas renvoyé sa configuration actuelle — impossible d’enregistrer en toute sécurité sans elle. Essayez d’actualiser et d’enregistrer à nouveau.
+
+
+ La configuration de la passerelle a été effacée en cours d’enregistrement. Essayez d’actualiser et d’enregistrer à nouveau.
+
+
+ Enregistrement bloqué pour {0}
+
+
+ La passerelle n’a pas répondu.
+
+
+ Échec de l’enregistrement pour {0}
+
+
+ La configuration de votre passerelle a été modifiée ailleurs (p. ex., depuis la page de configuration). Nous avons actualisé le cache — réessayez d’enregistrer. Détails : {0}
+
+
+ {0} Si cela ressemble à une incompatibilité de format filaire, ouvrez la page de configuration pour modifier directement le JSON.
+
+
+ Configuration de {0} enregistrée
+
+
+ En attente de la confirmation par la passerelle que le canal est en cours d’exécution…
+
+
+ Passerelle WSL
+
+
+ Ouvrez un shell ou gérez le service de passerelle locale dans WSL.
+
+
+ Invite de commandes
+
+
+ Démarrer
+
+
+ Arrêter
+
+
+ Redémarrer
+
+
+ Ouvrir le terminal
+
+
+ Ouvrir le terminal
+
+
+ Ouvrir le tableau de bord (dans le navigateur)
+
+
+ Ouvrir le tableau de bord
+
+
+ Déconnecter / reconnecter
+
+
+ Déconnecter ou reconnecter
+
+
+ Ouvrir le terminal
+
+
+ Ouvrir le terminal
+
+
+ Exécuter openclaw gateway start dans WSL
+
+
+ Démarrer la passerelle WSL
+
+
+ Exécuter openclaw gateway stop dans WSL
+
+
+ Arrêter la passerelle WSL
+
+
+ Exécuter openclaw gateway restart dans WSL
+
+
+ Redémarrer la passerelle WSL
+
+
diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
index 251ef4f0..c6631660 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
@@ -2285,7 +2285,7 @@ Voer op uw gateway-host (Mac/Linux) uit:
Gebruik
- Cron
+ Planning
Deze computer
@@ -3907,13 +3907,13 @@ Voer op uw gateway-host (Mac/Linux) uit:
PLANNING
-
+
Elke
-
+
Om
-
+
Cron
@@ -4578,4 +4578,906 @@ Voer op uw gateway-host (Mac/Linux) uit:
Geavanceerd
-
+
+
+ Actueel
+
+
+ Wissen
+
+
+ Gesprekken die deze gateway momenteel afhandelt, gegroepeerd per kanaal.
+
+
+ Sessies verschijnen hier wanneer een kanaal is verbonden en er verkeer loopt.
+
+
+ Chat openen
+
+
+ Ingeschakeld
+
+
+ Uitgeschakeld
+
+
+ Voorbeeld
+
+
+ Stemvoorbeeld
+
+
+ Niet verbonden
+
+
+ Opdrachten die de agent rechtstreeks op de gateway uitvoert, vallen hier niet onder. Als de gateway op deze pc staat, hebben ze mogelijk nog steeds toegang tot uw Windows-bestanden, klembord en netwerk — controleer de configuratie van uw gateway.
+
+ Alle agenten
+
+
+ {0} van {1} gebeurtenissen
+
+
+ Taak aanmaken
+
+
+ Taak bewerken
+
+
+ Nieuwe taak
+
+
+ Uitvoeren...
+
+
+ Wijzigingen opslaan
+
+
+ Item toevoegen
+
+
+ Bestand bijvoegen
+
+
+ Annuleren
+
+
+ Kan bestand niet bijvoegen
+
+
+ Kan geen verbinding maken met de gateway op {0}
+
+Controleer of de gateway actief is.
+
+
+ Chat kon niet worden gestart:
+{0}
+
+
+ Chat is gereed; uw eerste gesprek wordt gestart…
+
+
+ De gateway is verbonden; de chatinterface wordt nog geladen.
+
+
+ Het koppelen met de gateway is niet voltooid. Open de verbindingsinstellingen om het koppelen te voltooien.
+
+
+ Ongeldige gateway-URL: {0}
+
+
+ OK
+
+
+ Open de verbindingsinstellingen om het koppelen met een gateway te voltooien.
+
+
+ Spraakinstellingen openen
+
+
+ Time-out bij het wachten op chat op {0}. Probeer het opnieuw wanneer de gateway gereed is.
+
+
+ Time-out bij het wachten op de gateway-operatorhandshake. Probeer het opnieuw wanneer de gateway gereed is.
+
+
+ Het spraak-naar-tekstmodel moet worden geïnstalleerd voordat u spraakinvoer kunt gebruiken. Wilt u de spraakinstellingen openen om het te installeren?
+
+
+ Spraakmodel vereist
+
+
+ WebView2 kon niet worden geïnitialiseerd:
+{0}
+
+
+ Toepassen…
+
+
+ Wacht op goedkeuring
+
+
+ 🔐 Wacht op goedkeuring — keur goed en klik op Verbinden
+
+
+ 🔐 Wacht op goedkeuring van de gateway
+
+
+ Verbinden
+
+
+ ✓ Verbonden
+
+
+ ✓ Verbonden met {0}
+
+
+ Verbinden…
+
+
+ Verbinden (na goedkeuring)
+
+
+ uitgeschakeld
+
+
+ Verbinding verbroken
+
+
+ Voer een gateway-URL in
+
+
+ Geen gateways
+
+
+ Opnieuw verbinden…
+
+
+ afgewezen
+
+
+ SSH-tunnel starten…
+
+
+ op {0}
+
+
+ 1 verbonden client
+
+
+ {0} verbonden clients
+
+
+ {0}/{1} kanaal
+
+
+ {0}/{1} kanalen
+
+
+ ${0} vandaag
+
+
+ $0,00 vandaag
+
+
+ Gedeeld token
+
+
+ Tik om gedeeld token te kopiëren
+
+
+ via SSH-tunnel
+
+
+ Lokaal
+
+
+ Tailscale
+
+
+ Lokaal netwerk
+
+
+ Extern
+
+
+ Actief · {0} sessies
+
+
+ Actief · 1 sessie
+
+
+ Actief · geen actieve sessies
+
+
+ Niet verbonden
+
+
+ Verbinden…
+
+
+ Opnieuw verbinden…
+
+
+ Node actief · {0} mogelijkheden
+
+
+ Node actief · 1 mogelijkheid
+
+
+ Node actief · geen mogelijkheden ingeschakeld
+
+
+ Wacht op goedkeuring van koppeling
+
+
+ Koppeling geweigerd
+
+
+ Snelheidsbeperkt
+
+
+ Nodefout
+
+
+ Nodemodus uitgeschakeld
+
+
+ Geen mogelijkheden ingeschakeld. Kies wat u wilt delen bij Machtigingen.
+
+
+ Wacht op goedkeuring van de gatewayhost.
+
+
+ Koppeling is geweigerd. Koppel opnieuw via Gateway toevoegen.
+
+
+ Snelheidsbeperkt door de gateway. Wordt opnieuw geprobeerd na afkoeling.
+
+
+ De node heeft een fout gemeld.
+
+
+ Geen mogelijkheden aangeboden
+
+
+ 1 mogelijkheid aangeboden: {0}
+
+
+ {0} mogelijkheden aangeboden: {1}
+
+
+ Verbonden
+
+
+ Verbinden…
+
+
+ Wacht op goedkeuring
+
+
+ Verbinding verbreken…
+
+
+ Verbinden
+
+
+ Dashboard openen
+
+
+ Verbinding verbreken
+
+
+ Bewerken
+
+
+ Verwijderen
+
+
+ via SSH
+
+
+ Opgeslagen gateways (1)
+
+
+ Opgeslagen gateways ({0})
+
+
+ zojuist
+
+
+ {0} min. geleden
+
+
+ {0} uur geleden
+
+
+ {0} dagen geleden
+
+
+ {0} mnd. geleden
+
+
+ {0} jaar geleden
+
+
+ Deze gateway opnieuw koppelen:
+
+
+ Wacht op goedkeuring:
+
+
+ Help ons de SSH-tunnel te herstellen:
+
+
+ Help ons deze verbinding te herstellen:
+
+
+ Vraag een nieuwe installatiecode aan bij de gatewayhost.
+
+
+ Of plak hieronder een nieuw direct token.
+
+
+ Keur deze client goed op de gatewayhost met het onderstaande commando.
+
+
+ Zodra goedgekeurd, klik op Verbinden om door te gaan.
+
+
+ Bevestig dat SSH bereikbaar is op de host.
+
+
+ Herstart de tunnel hieronder als deze is gestopt.
+
+
+ Controleer de gatewaylogs op de host.
+
+
+ Als de gateway gedeeltelijk actief is, open het dashboard.
+
+
+ Bewerk de gatewayinstellingen om de URL of het token te wijzigen.
+
+
+ Controleer of de gateway draait op de host.
+
+
+ Controleer of u zich op hetzelfde netwerk of VPN bevindt.
+
+
+ Als u een SSH-tunnel gebruikt: bevestig dat deze actief is.
+
+
+ SSH-tunnel is uitgevallen.
+
+
+ Verbinding testen…
+
+
+ Gateway bereikbaar
+
+
+ Gateway antwoordde met {0}
+
+
+ Kan de gateway niet bereiken
+
+
+ Voer een gateway-URL in
+
+
+ Verbinden…
+
+
+ SSH-tunnel starten…
+
+
+ Koppelinggoedkeuring vereist voor {0}.
+
+
+ Verbonden met {0}.
+
+
+ SSH-externe poort moet 1–65535 zijn
+
+
+ SSH-lokale poort moet 1–65535 zijn
+
+
+ Herstart van tunnel geactiveerd.
+
+
+ Herstart van tunnel mislukt: {0}
+
+
+ Opnieuw gekoppeld. Opnieuw verbinden…
+
+
+ Kan de code niet toepassen.
+
+
+ Plak een installatiecode.
+
+
+ Toepassen…
+
+
+ Toegepast — gateway: {0}
+
+
+ Ongeldige installatiecode
+
+
+ Ongeldige URL
+
+
+ Verbinding mislukt
+
+
+ Onbekende fout
+
+
+ Gateway-adres: {0}
+
+
+ (niet opgegeven)
+
+
+ (geen token)
+
+
+ Toegangstoken: {0}
+
+
+ Druk op Scan starten om gateways op uw netwerk te zoeken.
+
+
+ Uw netwerk scannen…
+
+
+ Geen gateways gevonden op uw netwerk.
+
+
+ Gevonden op uw netwerk ({0})
+
+
+ Scan mislukt: {0}
+
+
+ Stoppen
+
+
+ Scannen
+
+
+ Toevoegen
+
+
+ Kies een gevonden gateway hierboven om te verbinden.
+
+
+ Gateway verwijderen?
+
+
+ Hiermee worden {0} en de bijbehorende referenties op deze machine vergeten.
+
+
+ Annuleren
+
+
+ Verbinding mislukt
+
+
+ Gatewayverbinding mislukt.
+
+
+ 1 goedkeuring wacht op u
+
+
+ {0} goedkeuringen wachten op u
+
+
+ Apparaat · {0}
+
+
+ Knooppunt · {0}
+
+
+ ⚠️ Reparatieverzoek
+
+
+ Goedkeuren
+
+
+ Weigeren
+
+
+ Koppelingverzoek goedkeuren
+
+
+ Koppelingverzoek weigeren
+
+
+ {0}
+
+Plak een nieuwe installatiecode vanuit het Gateway toevoegen-proces.
+
+
+ {0}
+
+Uw apparaat moet worden goedgekeurd op de gatewayhost.
+
+
+ {0}
+
+Deze gateway vereist wachtwoordverificatie.
+
+
+ {0}
+
+De gateway vereist mogelijk een andere versie van het verificatieprotocol.
+
+
+ {0}
+
+Controleer uw verbindingsinstellingen en probeer het opnieuw.
+
+
+ bootstrap-modus
+
+
+ gedeeld token
+
+
+ apparaattoken
+
+
+ onbekend
+
+
+ Time-out bij het wachten op de gatewayverbinding.
+
+
+ Dit kanaal vereist een macOS-host. Het kan niet worden geconfigureerd vanaf een Windows-machine.
+
+
+ Inloggegevens vervangen
+
+
+ Configuratie
+
+
+ Ontkoppel dit apparaat van het kanaal. Scan een nieuwe QR om opnieuw te verbinden — uw account blijft gekoppeld op uw telefoon.
+
+
+ Pauzeer het kanaal tijdelijk — inloggegevens worden bewaard zodat u het opnieuw kunt starten. Of verbreek de verbinding volledig om ze te wissen.
+
+
+ Start het kanaal — het begint berichten te verwerken met de opgeslagen inloggegevens. Of verbreek de verbinding volledig om ze te wissen.
+
+
+ Pauzeer het kanaal — inloggegevens worden bewaard zodat u het opnieuw kunt starten.
+
+
+ Start het kanaal — het begint berichten te verwerken met de opgeslagen inloggegevens.
+
+
+ Starten
+
+
+ Stoppen
+
+
+ Afmelden
+
+
+ Verbinding verbreken en inloggegevens vergeten
+
+
+ Besturing
+
+
+ WhatsApp gekoppelde apparaten hulp →
+
+
+ Signal gekoppelde apparaten hulp →
+
+
+ Een Telegram-bot aanmaken →
+
+
+ Een Discord-webhook aanmaken →
+
+
+ Een Google Chat-webhook toevoegen →
+
+
+ Slack-app-dashboard →
+
+
+ Over Nostr →
+
+
+ Over Berichten op macOS →
+
+
+ Koppel uw WhatsApp-telefoon
+
+
+ Klik op "Show QR" in het gedeelte Koppelen hieronder.
+
+
+ Op uw telefoon: WhatsApp → Instellingen → Gekoppelde apparaten → Apparaat koppelen.
+
+
+ Scan de QR-code die hier verschijnt.
+
+
+ Koppel uw Signal-telefoon
+
+
+ Klik op "Show QR" in het gedeelte Koppelen hieronder.
+
+
+ Op uw telefoon: Signal → Instellingen → Gekoppelde apparaten → Nieuw apparaat koppelen.
+
+
+ Scan de QR-code die hier verschijnt.
+
+
+ Telegram verbinden via een bot
+
+
+ Open Telegram en stuur een bericht naar @BotFather.
+
+
+ Stuur /newbot en volg de aanwijzingen. Kopieer het bot-token aan het einde.
+
+
+ Plak het token in het configuratieformulier hieronder.
+
+
+ Klik op "Save and start". Het kanaal start automatisch.
+
+
+ Discord verbinden via een webhook
+
+
+ Open uw Discord-serverinstellingen → Integraties → Webhooks.
+
+
+ Klik op "New Webhook", geef het een naam en kopieer de webhook-URL.
+
+
+ Plak de URL in het configuratieformulier hieronder.
+
+
+ Klik op "Save and start".
+
+
+ Google Chat verbinden via een webhook
+
+
+ Open in Google Chat de ruimte → Webhooks beheren → Webhook toevoegen.
+
+
+ Kopieer de webhook-URL.
+
+
+ Plak de URL in het configuratieformulier hieronder.
+
+
+ Klik op "Save and start".
+
+
+ Slack verbinden via een app
+
+
+ Maak een Slack-app aan op api.slack.com/apps en installeer deze in uw werkruimte.
+
+
+ Kopieer het bot-token (xoxb-…) en het ondertekeningsgeheim.
+
+
+ Plak beide in het configuratieformulier hieronder.
+
+
+ Klik op "Save and start".
+
+
+ Nostr verbinden via relays
+
+
+ Genereer of plak een privésleutel (nsec).
+
+
+ Kies een of meer relay-URL's (bijv. wss://relay.damus.io).
+
+
+ Plak beide in het configuratieformulier hieronder.
+
+
+ Klik op "Save and start".
+
+
+ iMessage is alleen beschikbaar op macOS
+
+
+ iMessage leest de lokale Messages.app-database, die alleen op macOS bestaat.
+
+
+ Om iMessage met OpenClaw te gebruiken, voert u de gateway uit op een Mac in plaats van deze Windows-host.
+
+
+ Alle andere kanalen in deze lijst werken prima vanaf een Windows-gehoste gateway.
+
+
+ Dit kanaal verbinden
+
+
+ "{0}" is een pluginkanaal. Raadpleeg de documentatie voor de vereiste velden.
+
+
+ Gebruik "Open Config page" in het gedeelte Configuratie hieronder om instellingen toe te voegen onder channels.{0}.
+
+
+ Sla de configuratie op; OpenClaw start het kanaal automatisch.
+
+
+ Bot-token
+
+
+ Webhook-URL
+
+
+ Ondertekeningsgeheim
+
+
+ Privésleutel (nsec)
+
+
+ Relay-URL's
+
+
+ Verkrijg het via @BotFather (/newbot).
+
+
+ Serverinstellingen → Integraties → Webhooks → New Webhook.
+
+
+ Open een ruimte → Webhooks beheren → Webhook toevoegen.
+
+
+ OAuth-tokens van uw Slack-app.
+
+
+ Basisinformatie → App-referenties.
+
+
+ Eén per regel.
+
+
+ Wijzigingen opslaan
+
+
+ Opslaan en starten
+
+
+ Configuratiepagina openen
+
+
+ Bewerk de instellingen van dit kanaal op de configuratiepagina van de gateway.
+
+
+ Sla na het volgen van de bovenstaande stappen de configuratie op om het kanaal te starten.
+
+
+ Niet verbonden
+
+
+ Maak verbinding met een gateway voordat u de kanaalconfiguratie opslaat.
+
+
+ Ontbrekend veld
+
+
+ {0} is vereist.
+
+
+ {0} opslaan…
+
+
+ {0} veld(en) schrijven en het kanaal inschakelen.
+
+
+ Kan gatewayconfiguratie niet laden
+
+
+ De gateway heeft de huidige configuratie niet geretourneerd — kan niet veilig opslaan zonder deze. Probeer te vernieuwen en opnieuw op te slaan.
+
+
+ De configuratie van de gateway is tijdens het opslaan gewist. Probeer te vernieuwen en opnieuw op te slaan.
+
+
+ Opslaan geblokkeerd voor {0}
+
+
+ De gateway heeft niet gereageerd.
+
+
+ Opslaan mislukt voor {0}
+
+
+ Uw gatewayconfiguratie is elders gewijzigd (bijv. vanaf de configuratiepagina). We hebben de cache vernieuwd — probeer opnieuw op te slaan. Details: {0}
+
+
+ {0} Als dit lijkt op een mismatch in het draadformaat, open dan de configuratiepagina voor directe JSON-bewerking.
+
+
+ Configuratie van {0} opgeslagen
+
+
+ Wachten tot de gateway bevestigt dat het kanaal actief is…
+
+
+ WSL-gateway
+
+
+ Open een shell of beheer de lokale gatewayservice in WSL.
+
+
+ Terminalvenster
+
+
+ Starten
+
+
+ Stoppen
+
+
+ Opnieuw starten
+
+
+ Terminal openen
+
+
+ Terminal openen
+
+
+ Dashboard openen (in browser)
+
+
+ Dashboard openen
+
+
+ Verbinding verbreken / opnieuw verbinden
+
+
+ Verbinding verbreken of opnieuw verbinden
+
+
+ Terminal openen
+
+
+ Terminal openen
+
+
+ openclaw gateway start uitvoeren in WSL
+
+
+ WSL-gateway starten
+
+
+ openclaw gateway stop uitvoeren in WSL
+
+
+ WSL-gateway stoppen
+
+
+ openclaw gateway restart uitvoeren in WSL
+
+
+ WSL-gateway opnieuw starten
+
+
diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
index 88945594..106bb16a 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
@@ -2284,7 +2284,7 @@
用量
- Cron
+ 计划任务
此计算机
@@ -3906,13 +3906,13 @@
计划
-
+
每
-
+
于
-
+
Cron
@@ -4577,4 +4577,907 @@
高级
-
+
+
+ 实时
+
+
+ 清除
+
+
+ 此网关当前正在处理的对话,按频道分组。
+
+
+ 当频道已连接且有流量时,会话将显示在此处。
+
+
+ 打开聊天
+
+
+ 已启用
+
+
+ 已禁用
+
+
+ 预览
+
+
+ 语音预览
+
+
+ 已断开连接
+
+
+ 代理直接在网关上运行的命令不受此限制。如果网关在此电脑上,它们仍可能访问您的 Windows 文件、剪贴板和网络——请检查网关的配置。
+
+
+ 所有代理
+
+
+ {0} / {1} 个事件
+
+
+ 创建任务
+
+
+ 编辑任务
+
+
+ 新任务
+
+
+ 正在运行...
+
+
+ 保存更改
+
+
+ 添加项目
+
+
+ 附加文件
+
+
+ 取消
+
+
+ 无法附加文件
+
+
+ 无法连接到 {0} 的网关
+
+请确保网关正在运行。
+
+
+ 聊天启动失败:
+{0}
+
+
+ 聊天已准备就绪;正在启动您的首次对话…
+
+
+ 网关已连接;聊天界面仍在加载中。
+
+
+ 网关配对未完成。请打开连接设置以完成配对。
+
+
+ 无效的网关 URL:{0}
+
+
+ 确定
+
+
+ 打开连接设置以完成与网关的配对。
+
+
+ 打开语音设置
+
+
+ 等待 {0} 的聊天超时。请在网关准备就绪后重试。
+
+
+ 等待网关操作员握手超时。请在网关准备就绪后重试。
+
+
+ 需要先安装语音转文字模型才能使用语音输入。是否要打开语音设置进行安装?
+
+
+ 需要语音模型
+
+
+ WebView2 初始化失败:
+{0}
+
+
+ 正在应用…
+
+
+ 等待批准
+
+
+ 🔐 等待批准 - 批准后请点击连接
+
+
+ 🔐 等待网关批准
+
+
+ 连接
+
+
+ ✓ 已连接
+
+
+ ✓ 已连接到 {0}
+
+
+ 正在连接…
+
+
+ 连接(批准后)
+
+
+ 已禁用
+
+
+ 已断开连接
+
+
+ 输入网关 URL
+
+
+ 没有网关
+
+
+ 正在重新连接…
+
+
+ 已拒绝
+
+
+ 正在启动 SSH 隧道…
+
+
+ 在 {0} 上
+
+
+ 1 个客户端
+
+
+ {0} 个客户端
+
+
+ {0}/{1} 个频道
+
+
+ {0}/{1} 个频道
+
+
+ 今日 ${0}
+
+
+ 今日 $0.00
+
+
+ 共享令牌
+
+
+ 点击以复制共享令牌
+
+
+ 通过 SSH 隧道
+
+
+ 本地
+
+
+ Tailscale
+
+
+ 局域网
+
+
+ 远程
+
+
+ 活动 · {0} 个会话
+
+
+ 活动 · 1 个会话
+
+
+ 活动 · 无当前会话
+
+
+ 已断开连接
+
+
+ 正在连接…
+
+
+ 正在重新连接…
+
+
+ 节点活动 · {0} 项能力
+
+
+ 节点活动 · 1 项能力
+
+
+ 节点活动 · 无已启用的能力
+
+
+ 等待配对审批
+
+
+ 配对被拒绝
+
+
+ 已限流
+
+
+ 节点错误
+
+
+ 节点模式已禁用
+
+
+ 未启用任何能力。请在权限中选择要共享的内容。
+
+
+ 等待网关主机上的审批。
+
+
+ 配对被拒绝。请从添加网关重新配对。
+
+
+ 被网关限流。将在冷却后重试。
+
+
+ 节点报告了一个错误。
+
+
+ 未提供任何能力
+
+
+ 提供 1 项能力:{0}
+
+
+ 提供 {0} 项能力:{1}
+
+
+ 已连接
+
+
+ 正在连接…
+
+
+ 等待审批
+
+
+ 正在断开连接…
+
+
+ 连接
+
+
+ 打开仪表板
+
+
+ 断开连接
+
+
+ 编辑
+
+
+ 移除
+
+
+ 通过 SSH
+
+
+ 已保存的网关 (1)
+
+
+ 已保存的网关 ({0})
+
+
+ 刚刚
+
+
+ {0}分钟前
+
+
+ {0}小时前
+
+
+ {0}天前
+
+
+ {0}个月前
+
+
+ {0}年前
+
+
+ 重新配对此网关:
+
+
+ 等待审批:
+
+
+ 帮助我们修复 SSH 隧道:
+
+
+ 帮助我们修复此连接:
+
+
+ 从网关主机获取新的设置代码。
+
+
+ 或在下方粘贴新的直接令牌。
+
+
+ 使用以下命令在网关主机上批准此客户端。
+
+
+ 批准后,点击连接以继续。
+
+
+ 确认主机上的 SSH 可访问。
+
+
+ 如果隧道已停止,请在下方重新启动。
+
+
+ 检查主机上的网关日志。
+
+
+ 如果网关部分运行,请打开其仪表板。
+
+
+ 编辑网关设置以更改 URL 或令牌。
+
+
+ 检查网关是否在主机上运行。
+
+
+ 检查您是否在同一网络或 VPN 上。
+
+
+ 如果使用 SSH 隧道:确认其已启动。
+
+
+ SSH 隧道已断开。
+
+
+ 正在测试连接…
+
+
+ 网关可访问
+
+
+ 网关响应代码为 {0}
+
+
+ 无法访问网关
+
+
+ 输入网关 URL
+
+
+ 正在连接…
+
+
+ 正在启动 SSH 隧道…
+
+
+ 需要对 {0} 进行配对审批。
+
+
+ 已连接到 {0}。
+
+
+ SSH 远程端口必须在 1–65535 之间
+
+
+ SSH 本地端口必须在 1–65535 之间
+
+
+ 隧道重启已触发。
+
+
+ 隧道重启失败:{0}
+
+
+ 已重新配对。正在重新连接…
+
+
+ 无法应用代码。
+
+
+ 请粘贴设置代码。
+
+
+ 正在应用…
+
+
+ 已应用 — 网关:{0}
+
+
+ 设置代码无效
+
+
+ URL 无效
+
+
+ 连接失败
+
+
+ 未知错误
+
+
+ 网关:{0}
+
+
+ (未指定)
+
+
+ (无令牌)
+
+
+ 令牌:{0}
+
+
+ 按“开始扫描”以在您的网络上查找网关。
+
+
+ 正在扫描您的网络…
+
+
+ 未在您的网络上找到网关。
+
+
+ 在您的网络上发现 ({0})
+
+
+ 扫描失败:{0}
+
+
+ 停止
+
+
+ 扫描
+
+
+ 添加
+
+
+ 选择上方发现的网关进行连接。
+
+
+ 移除网关?
+
+
+ 这将移除此计算机上的 {0} 及其凭据。
+
+
+ 取消
+
+
+ 连接失败
+
+
+ 网关连接失败。
+
+
+ 1 项审批等待您处理
+
+
+ {0} 项审批等待您处理
+
+
+ 设备 · {0}
+
+
+ 节点 · {0}
+
+
+ ⚠️ 修复请求
+
+
+ 批准
+
+
+ 拒绝
+
+
+ 批准配对请求
+
+
+ 拒绝配对请求
+
+
+ {0}
+
+从添加网关流程中粘贴新的设置代码。
+
+
+ {0}
+
+您的设备需要在网关主机上获得批准。
+
+
+ {0}
+
+此网关需要密码身份验证。
+
+
+ {0}
+
+网关可能需要不同版本的认证协议。
+
+
+ {0}
+
+请检查您的连接设置并重试。
+
+
+ 引导
+
+
+ 共享令牌
+
+
+ 设备令牌
+
+
+ 未知
+
+
+ 等待网关连接完成超时。
+
+
+ 此频道需要 macOS 主机,无法从 Windows 计算机进行配置。
+
+
+ 替换凭据
+
+
+ 配置
+
+
+ 将此设备与频道取消关联。扫描新的 QR 码以重新连接——您的帐户仍会在手机上保持配对。
+
+
+ 暂时暂停频道——凭据会被保留,以便您可以重新启动。或完全断开连接以清除凭据。
+
+
+ 启动频道——它将使用已存储的凭据开始处理消息。或完全断开连接以清除凭据。
+
+
+ 暂停频道——凭据会被保留,以便您可以重新启动。
+
+
+ 启动频道——它将使用已存储的凭据开始处理消息。
+
+
+ 启动
+
+
+ 停止
+
+
+ 注销
+
+
+ 断开连接并清除凭据
+
+
+ 控制
+
+
+ WhatsApp 已关联设备帮助 →
+
+
+ Signal 已关联设备帮助 →
+
+
+ 如何创建 Telegram 机器人 →
+
+
+ 如何创建 Discord webhook →
+
+
+ 如何添加 Google Chat webhook →
+
+
+ Slack 应用控制台 →
+
+
+ 关于 Nostr →
+
+
+ 关于 macOS“信息”→
+
+
+ 关联您的 WhatsApp 手机
+
+
+ 点击下方“关联”部分中的 "Show QR"。
+
+
+ 在手机上:WhatsApp → 设置 → 已关联设备 → 关联设备。
+
+
+ 扫描此处显示的 QR 码。
+
+
+ 关联您的 Signal 手机
+
+
+ 点击下方“关联”部分中的 "Show QR"。
+
+
+ 在手机上:Signal → 设置 → 已关联设备 → 关联新设备。
+
+
+ 扫描此处显示的 QR 码。
+
+
+ 通过机器人连接 Telegram
+
+
+ 打开 Telegram 并向 @BotFather 发送消息。
+
+
+ 发送 /newbot 并按照提示操作。最后复制机器人令牌。
+
+
+ 将令牌粘贴到下方的配置表单中。
+
+
+ 点击 "Save and start"。频道将自动启动。
+
+
+ 通过 webhook 连接 Discord
+
+
+ 打开您的 Discord 服务器设置 → 集成 → Webhooks。
+
+
+ 点击 "New Webhook",为其命名,然后复制 webhook URL。
+
+
+ 将 URL 粘贴到下方的配置表单中。
+
+
+ 点击 "Save and start"。
+
+
+ 通过 webhook 连接 Google Chat
+
+
+ 在 Google Chat 中,打开聊天室 → 管理 webhook → 添加 webhook。
+
+
+ 复制 webhook URL。
+
+
+ 将 URL 粘贴到下方的配置表单中。
+
+
+ 点击 "Save and start"。
+
+
+ 通过应用连接 Slack
+
+
+ 在 api.slack.com/apps 上创建 Slack 应用并将其安装到您的工作区。
+
+
+ 复制机器人令牌 (xoxb-…) 和签名密钥。
+
+
+ 将两者粘贴到下方的配置表单中。
+
+
+ 点击 "Save and start"。
+
+
+ 通过中继连接 Nostr
+
+
+ 生成或粘贴私钥 (nsec)。
+
+
+ 选择一个或多个中继 URL(例如 wss://relay.damus.io)。
+
+
+ 将两者粘贴到下方的配置表单中。
+
+
+ 点击 "Save and start"。
+
+
+ iMessage 仅适用于 macOS
+
+
+ iMessage 读取本地 Messages.app 数据库,该数据库仅存在于 macOS 上。
+
+
+ 若要将 iMessage 与 OpenClaw 配合使用,请在 Mac 上运行网关,而非此 Windows 主机。
+
+
+ 此列表中的所有其他频道都可以在 Windows 托管的网关上正常使用。
+
+
+ 连接此频道
+
+
+ "{0}" 是一个插件频道。请参阅其文档以了解所需字段。
+
+
+ 使用下方“配置”部分中的 "Open Config page" 在 channels.{0} 下添加设置。
+
+
+ 保存配置;OpenClaw 将自动启动频道。
+
+
+ 机器人令牌
+
+
+ Webhook 网址
+
+
+ 签名密钥
+
+
+ 私钥 (nsec)
+
+
+ 中继 URL
+
+
+ 从 @BotFather (/newbot) 获取。
+
+
+ 服务器设置 → 集成 → Webhooks → New Webhook。
+
+
+ 打开聊天室 → 管理 webhook → 添加 webhook。
+
+
+ 来自您 Slack 应用的 OAuth 令牌。
+
+
+ Basic Information → App Credentials。
+
+
+ 每行一个。
+
+
+ 保存更改
+
+
+ 保存并启动
+
+
+ 打开配置页面
+
+
+ 在网关配置页面中编辑此频道的设置。
+
+
+ 按照上述步骤操作后,保存配置以启动频道。
+
+
+ 未连接
+
+
+ 请先连接到网关,然后再保存频道配置。
+
+
+ 缺少字段
+
+
+ {0} 为必填项。
+
+
+ 正在保存 {0}…
+
+
+ 正在写入 {0} 个字段并启用频道。
+
+
+ 无法加载网关配置
+
+
+ 网关未返回其当前配置——没有它无法安全保存。请尝试刷新并重新保存。
+
+
+ 网关配置在保存过程中被清除。请尝试刷新并重新保存。
+
+
+ {0} 的保存被阻止
+
+
+ 网关未响应。
+
+
+ {0} 的保存失败
+
+
+ 您的网关配置已在其他位置更改(例如,从配置页面)。我们已刷新缓存——请再次尝试保存。详细信息:{0}
+
+
+ {0} 如果这看起来像是线路格式不匹配,请打开配置页面进行直接 JSON 编辑。
+
+
+ {0} 配置已保存
+
+
+ 正在等待网关确认频道已运行…
+
+
+ WSL 网关
+
+
+ 打开 shell,或在 WSL 中管理本地网关服务。
+
+
+ 终端
+
+
+ 启动
+
+
+ 停止
+
+
+ 重启
+
+
+ 打开终端
+
+
+ 打开终端
+
+
+ 在浏览器中打开仪表板
+
+
+ 打开仪表板
+
+
+ 断开/重新连接
+
+
+ 断开连接或重新连接
+
+
+ 打开终端
+
+
+ 打开终端
+
+
+ 在 WSL 中运行 openclaw gateway start
+
+
+ 启动 WSL 网关
+
+
+ 在 WSL 中运行 openclaw gateway stop
+
+
+ 停止 WSL 网关
+
+
+ 在 WSL 中运行 openclaw gateway restart
+
+
+ 重启 WSL 网关
+
+
diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
index 4e8bbefd..e22f5630 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
@@ -2284,7 +2284,7 @@
用量
- Cron
+ 排程工作
這部電腦
@@ -3906,13 +3906,13 @@
排程
-
+
每
-
+
於
-
+
Cron
@@ -4577,4 +4577,907 @@
進階
-
+
+
+ 即時
+
+
+ 清除
+
+
+ 此閘道目前正在處理的對話,依頻道分組。
+
+
+ 當頻道已連線且有流量時,工作階段將顯示在此處。
+
+
+ 開啟聊天
+
+
+ 已啟用
+
+
+ 已停用
+
+
+ 預覽
+
+
+ 語音預覽
+
+
+ 已中斷連線
+
+
+ 代理直接在閘道上執行的命令不受此限制。如果閘道在此電腦上,它們仍可能存取您的 Windows 檔案、剪貼簿和網路——請檢查閘道的設定。
+
+
+ 所有代理
+
+
+ {0} / {1} 個事件
+
+
+ 建立任務
+
+
+ 編輯任務
+
+
+ 新任務
+
+
+ 正在執行...
+
+
+ 儲存變更
+
+
+ 新增項目
+
+
+ 附加檔案
+
+
+ 取消
+
+
+ 無法附加檔案
+
+
+ 無法連線到 {0} 的閘道
+
+請確認閘道正在運行。
+
+
+ 聊天啟動失敗:
+{0}
+
+
+ 聊天已準備就緒;正在啟動您的首次對話…
+
+
+ 閘道已連線;聊天介面仍在載入中。
+
+
+ 閘道配對未完成。請開啟連線設定以完成配對。
+
+
+ 無效的閘道 URL:{0}
+
+
+ 確定
+
+
+ 開啟連線設定以完成與閘道的配對。
+
+
+ 開啟語音設定
+
+
+ 等待 {0} 的聊天逾時。請在閘道準備就緒後重試。
+
+
+ 等待閘道操作員交握逾時。請在閘道準備就緒後重試。
+
+
+ 需要先安裝語音轉文字模型才能使用語音輸入。是否要開啟語音設定進行安裝?
+
+
+ 需要語音模型
+
+
+ WebView2 初始化失敗:
+{0}
+
+
+ 正在套用…
+
+
+ 等待核准
+
+
+ 🔐 等待核准 — 核准後請點擊連線
+
+
+ 🔐 等待閘道核准
+
+
+ 連線
+
+
+ ✓ 已連線
+
+
+ ✓ 已連線到 {0}
+
+
+ 正在連線…
+
+
+ 連線(核准後)
+
+
+ 已停用
+
+
+ 已中斷連線
+
+
+ 輸入閘道 URL
+
+
+ 沒有閘道
+
+
+ 正在重新連線…
+
+
+ 已拒絕
+
+
+ 正在啟動 SSH 通道…
+
+
+ 在 {0} 上
+
+
+ 1 個用戶端
+
+
+ {0} 個用戶端
+
+
+ {0}/{1} 個頻道
+
+
+ {0}/{1} 個頻道
+
+
+ 今日 ${0}
+
+
+ 今日 $0.00
+
+
+ 共用權杖
+
+
+ 點擊以複製共用權杖
+
+
+ 透過 SSH 通道
+
+
+ 本機
+
+
+ Tailscale
+
+
+ 區域網路
+
+
+ 遠端
+
+
+ 使用中 · {0} 個工作階段
+
+
+ 使用中 · 1 個工作階段
+
+
+ 使用中 · 目前無工作階段
+
+
+ 已中斷連線
+
+
+ 連線中…
+
+
+ 重新連線中…
+
+
+ 節點使用中 · {0} 項功能
+
+
+ 節點使用中 · 1 項功能
+
+
+ 節點使用中 · 未啟用任何功能
+
+
+ 等待配對核准
+
+
+ 配對遭拒
+
+
+ 已限流
+
+
+ 節點錯誤
+
+
+ 節點模式已停用
+
+
+ 未啟用任何功能。請在權限中選擇要共用的內容。
+
+
+ 等待閘道主機上的核准。
+
+
+ 配對遭拒。請從新增閘道重新配對。
+
+
+ 被閘道限流。將在冷卻後重試。
+
+
+ 節點回報了一個錯誤。
+
+
+ 未提供任何功能
+
+
+ 提供 1 項功能:{0}
+
+
+ 提供 {0} 項功能:{1}
+
+
+ 已連線
+
+
+ 連線中…
+
+
+ 等待核准
+
+
+ 正在中斷連線…
+
+
+ 連線
+
+
+ 開啟儀表板
+
+
+ 中斷連線
+
+
+ 編輯
+
+
+ 移除
+
+
+ 透過 SSH
+
+
+ 已儲存的閘道 (1)
+
+
+ 已儲存的閘道 ({0})
+
+
+ 剛才
+
+
+ {0}分鐘前
+
+
+ {0}小時前
+
+
+ {0}天前
+
+
+ {0}個月前
+
+
+ {0}年前
+
+
+ 重新配對此閘道:
+
+
+ 等待核准:
+
+
+ 協助我們修復 SSH 通道:
+
+
+ 協助我們修復此連線:
+
+
+ 從閘道主機取得新的設定碼。
+
+
+ 或在下方貼上新的直接權杖。
+
+
+ 使用以下命令在閘道主機上核准此用戶端。
+
+
+ 核准後,按一下連線以繼續。
+
+
+ 確認主機上的 SSH 可存取。
+
+
+ 如果通道已停止,請在下方重新啟動。
+
+
+ 檢查主機上的閘道記錄。
+
+
+ 如果閘道部分運作中,請開啟其儀表板。
+
+
+ 編輯閘道設定以變更 URL 或權杖。
+
+
+ 檢查閘道是否在主機上執行。
+
+
+ 檢查您是否在相同的網路或 VPN 上。
+
+
+ 如果使用 SSH 通道:確認其已啟動。
+
+
+ SSH 通道已中斷。
+
+
+ 正在測試連線…
+
+
+ 閘道可存取
+
+
+ 閘道回應代碼為 {0}
+
+
+ 無法存取閘道
+
+
+ 輸入閘道 URL
+
+
+ 連線中…
+
+
+ 正在啟動 SSH 通道…
+
+
+ 需要對 {0} 進行配對核准。
+
+
+ 已連線到 {0}。
+
+
+ SSH 遠端連接埠必須在 1–65535 之間
+
+
+ SSH 本機連接埠必須在 1–65535 之間
+
+
+ 通道重新啟動已觸發。
+
+
+ 通道重新啟動失敗:{0}
+
+
+ 已重新配對。重新連線中…
+
+
+ 無法套用代碼。
+
+
+ 請貼上設定碼。
+
+
+ 正在套用…
+
+
+ 已套用 — 閘道:{0}
+
+
+ 設定碼無效
+
+
+ URL 無效
+
+
+ 連線失敗
+
+
+ 未知錯誤
+
+
+ 閘道:{0}
+
+
+ (未指定)
+
+
+ (無權杖)
+
+
+ 權杖:{0}
+
+
+ 按下開始掃描以在您的網路上尋找閘道。
+
+
+ 正在掃描您的網路…
+
+
+ 未在您的網路上找到閘道。
+
+
+ 在您的網路上發現 ({0})
+
+
+ 掃描失敗:{0}
+
+
+ 停止
+
+
+ 掃描
+
+
+ 新增
+
+
+ 選擇上方發現的閘道進行連線。
+
+
+ 移除閘道?
+
+
+ 這將移除此電腦上的 {0} 及其認證。
+
+
+ 取消
+
+
+ 連線失敗
+
+
+ 閘道連線失敗。
+
+
+ 1 項核准等待您處理
+
+
+ {0} 項核准等待您處理
+
+
+ 裝置 · {0}
+
+
+ 節點 · {0}
+
+
+ ⚠️ 修復請求
+
+
+ 核准
+
+
+ 拒絕
+
+
+ 核准配對請求
+
+
+ 拒絕配對請求
+
+
+ {0}
+
+從新增閘道流程中貼上新的設定碼。
+
+
+ {0}
+
+您的裝置需要在閘道主機上獲得核准。
+
+
+ {0}
+
+此閘道需要密碼驗證。
+
+
+ {0}
+
+閘道可能需要不同版本的認證協定。
+
+
+ {0}
+
+請檢查您的連線設定並重試。
+
+
+ 引導
+
+
+ 共用權杖
+
+
+ 裝置權杖
+
+
+ 未知
+
+
+ 等待閘道連線完成逾時。
+
+
+ 此頻道需要 macOS 主機,無法從 Windows 電腦進行設定。
+
+
+ 替換憑證
+
+
+ 設定
+
+
+ 將此裝置與頻道取消連結。掌描新的 QR 碼以重新連線——您的帳戶仍會在手機上保持配對。
+
+
+ 暫時暫停頻道——憑證會被保留,以便您可以重新啟動。或完全中斷連線以清除憑證。
+
+
+ 啟動頻道——它將使用已儲存的憑證開始處理訊息。或完全中斷連線以清除憑證。
+
+
+ 暫停頻道——憑證會被保留,以便您可以重新啟動。
+
+
+ 啟動頻道——它將使用已儲存的憑證開始處理訊息。
+
+
+ 啟動
+
+
+ 停止
+
+
+ 登出
+
+
+ 中斷連線並清除憑證
+
+
+ 控制
+
+
+ WhatsApp 已連結裝置說明 →
+
+
+ Signal 已連結裝置說明 →
+
+
+ 如何建立 Telegram 機器人 →
+
+
+ 如何建立 Discord webhook →
+
+
+ 如何新增 Google Chat webhook →
+
+
+ Slack 應用程式控制台 →
+
+
+ 關於 Nostr →
+
+
+ 關於 macOS「訊息」→
+
+
+ 連結您的 WhatsApp 手機
+
+
+ 點擊下方「連結」區段中的 "Show QR"。
+
+
+ 在手機上:WhatsApp → 設定 → 已連結裝置 → 連結裝置。
+
+
+ 掌描此處顯示的 QR 碼。
+
+
+ 連結您的 Signal 手機
+
+
+ 點擊下方「連結」區段中的 "Show QR"。
+
+
+ 在手機上:Signal → 設定 → 已連結裝置 → 連結新裝置。
+
+
+ 掌描此處顯示的 QR 碼。
+
+
+ 透過機器人連接 Telegram
+
+
+ 開啟 Telegram 並向 @BotFather 傳送訊息。
+
+
+ 傳送 /newbot 並按照提示操作。最後複製機器人權杖。
+
+
+ 將權杖貼到下方的設定表單中。
+
+
+ 點擊 "Save and start"。頻道將自動啟動。
+
+
+ 透過 webhook 連接 Discord
+
+
+ 開啟您的 Discord 伺服器設定 → 整合 → Webhooks。
+
+
+ 點擊 "New Webhook",為其命名,然後複製 webhook URL。
+
+
+ 將 URL 貼到下方的設定表單中。
+
+
+ 點擊 "Save and start"。
+
+
+ 透過 webhook 連接 Google Chat
+
+
+ 在 Google Chat 中,開啟聊天室 → 管理 webhook → 新增 webhook。
+
+
+ 複製 webhook URL。
+
+
+ 將 URL 貼到下方的設定表單中。
+
+
+ 點擊 "Save and start"。
+
+
+ 透過應用程式連接 Slack
+
+
+ 在 api.slack.com/apps 上建立 Slack 應用程式並將其安裝到您的工作區。
+
+
+ 複製機器人權杖 (xoxb-…) 和簽署密鑰。
+
+
+ 將兩者貼到下方的設定表單中。
+
+
+ 點擊 "Save and start"。
+
+
+ 透過中繼連接 Nostr
+
+
+ 產生或貼上私鑰 (nsec)。
+
+
+ 選擇一個或多個中繼 URL(例如 wss://relay.damus.io)。
+
+
+ 將兩者貼到下方的設定表單中。
+
+
+ 點擊 "Save and start"。
+
+
+ iMessage 僅適用於 macOS
+
+
+ iMessage 讀取本機 Messages.app 資料庫,該資料庫僅存在於 macOS 上。
+
+
+ 若要將 iMessage 與 OpenClaw 搭配使用,請在 Mac 上執行閘道,而非此 Windows 主機。
+
+
+ 此列表中的所有其他頻道都可以在 Windows 託管的閘道上正常使用。
+
+
+ 連接此頻道
+
+
+ "{0}" 是一個外掛頻道。請參閱其文件以了解所需欄位。
+
+
+ 使用下方「設定」區段中的 "Open Config page" 在 channels.{0} 下新增設定。
+
+
+ 儲存設定;OpenClaw 將自動啟動頻道。
+
+
+ 機器人權杖
+
+
+ Webhook 網址
+
+
+ 簽署密鑰
+
+
+ 私鑰 (nsec)
+
+
+ 中繼 URL
+
+
+ 從 @BotFather (/newbot) 取得。
+
+
+ 伺服器設定 → 整合 → Webhooks → New Webhook。
+
+
+ 開啟聊天室 → 管理 webhook → 新增 webhook。
+
+
+ 來自您 Slack 應用程式的 OAuth 權杖。
+
+
+ Basic Information → App Credentials。
+
+
+ 每行一個。
+
+
+ 儲存變更
+
+
+ 儲存並啟動
+
+
+ 開啟設定頁面
+
+
+ 在閘道設定頁面中編輯此頻道的設定。
+
+
+ 按照上述步驟操作後,儲存設定以啟動頻道。
+
+
+ 未連線
+
+
+ 請先連線到閘道,然後再儲存頻道設定。
+
+
+ 缺少欄位
+
+
+ {0} 為必填項。
+
+
+ 正在儲存 {0}…
+
+
+ 正在寫入 {0} 個欄位並啟用頻道。
+
+
+ 無法載入閘道設定
+
+
+ 閘道未傳回其目前設定——沒有它無法安全儲存。請嘗試重新整理並再次儲存。
+
+
+ 閘道設定在儲存過程中被清除。請嘗試重新整理並再次儲存。
+
+
+ {0} 的儲存被封鎖
+
+
+ 閘道未回應。
+
+
+ {0} 的儲存失敗
+
+
+ 您的閘道設定已在其他位置變更(例如,從設定頁面)。我們已重新整理快取——請再次嘗試儲存。詳細資訊:{0}
+
+
+ {0} 如果這看起來像是線路格式不符,請開啟設定頁面進行直接 JSON 編輯。
+
+
+ {0} 設定已儲存
+
+
+ 正在等待閘道確認頻道已執行…
+
+
+ WSL 閘道
+
+
+ 開啟 shell,或在 WSL 中管理本機閘道服務。
+
+
+ 終端機
+
+
+ 啟動
+
+
+ 停止
+
+
+ 重新啟動
+
+
+ 開啟終端機
+
+
+ 開啟終端機
+
+
+ 在瀏覽器中開啟儀表板
+
+
+ 開啟儀表板
+
+
+ 中斷連線/重新連線
+
+
+ 中斷連線或重新連線
+
+
+ 開啟終端機
+
+
+ 開啟終端機
+
+
+ 在 WSL 中執行 openclaw gateway start
+
+
+ 啟動 WSL 閘道
+
+
+ 在 WSL 中執行 openclaw gateway stop
+
+
+ 停止 WSL 閘道
+
+
+ 在 WSL 中執行 openclaw gateway restart
+
+
+ 重新啟動 WSL 閘道
+
+
diff --git a/src/OpenClaw.Tray.WinUI/Windows/ConnectionStatusWindow.xaml.cs b/src/OpenClaw.Tray.WinUI/Windows/ConnectionStatusWindow.xaml.cs
index 04c4354e..be7d37e5 100644
--- a/src/OpenClaw.Tray.WinUI/Windows/ConnectionStatusWindow.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/Windows/ConnectionStatusWindow.xaml.cs
@@ -86,18 +86,18 @@ private void OnManagerStateChanged(object? sender, GatewayConnectionSnapshot sna
// Update connect button and status based on state
if (snapshot.OverallState == OverallConnectionState.PairingRequired)
{
- ConnectButton.Content = "Connect (once approved)";
- SetupCodeResult.Text = "🔐 Awaiting approval from gateway";
- DirectConnectResult.Text = "🔐 Awaiting approval — approve then click Connect";
+ ConnectButton.Content = LocalizationHelper.GetString("ConnectionStatus_ConnectOnceApproved");
+ SetupCodeResult.Text = LocalizationHelper.GetString("ConnectionStatus_AwaitingApprovalFromGateway");
+ DirectConnectResult.Text = LocalizationHelper.GetString("ConnectionStatus_AwaitingApprovalApproveThenConnect");
}
else if (snapshot.OverallState is OverallConnectionState.Connected or OverallConnectionState.Ready)
{
- ConnectButton.Content = "Connect";
- DirectConnectResult.Text = "✓ Connected";
+ ConnectButton.Content = LocalizationHelper.GetString("ConnectionStatus_Connect");
+ DirectConnectResult.Text = LocalizationHelper.GetString("ConnectionStatus_Connected");
}
else
{
- ConnectButton.Content = "Connect";
+ ConnectButton.Content = LocalizationHelper.GetString("ConnectionStatus_Connect");
}
});
}
@@ -137,7 +137,7 @@ private void RefreshStateMachine(GatewayConnectionSnapshot snapshot)
{
RoleConnectionState.Connected => $"✓ {elapsedStr} device={snapshot.OperatorDeviceId ?? "—"}",
RoleConnectionState.Error => $"✗ {elapsedStr} — {snapshot.OperatorError ?? "unknown"}",
- RoleConnectionState.PairingRequired => $"⏳ Awaiting approval",
+ RoleConnectionState.PairingRequired => $"⏳ {LocalizationHelper.GetString("ConnectionStatus_AwaitingApproval")}",
_ => elapsedStr
};
@@ -152,9 +152,9 @@ private void RefreshStateMachine(GatewayConnectionSnapshot snapshot)
snapshot.NodeState is RoleConnectionState.PairingRequired or RoleConnectionState.PairingRejected, AmberBrush);
NodeDetailText.Text = snapshot.NodeState switch
{
- RoleConnectionState.Disabled => "disabled",
+ RoleConnectionState.Disabled => LocalizationHelper.GetString("ConnectionStatus_Disabled"),
RoleConnectionState.Error => snapshot.NodeError ?? "error",
- RoleConnectionState.PairingRejected => "rejected",
+ RoleConnectionState.PairingRejected => LocalizationHelper.GetString("ConnectionStatus_Rejected"),
_ => ""
};
}
@@ -175,7 +175,7 @@ private void RefreshGateways()
{
GatewayListPanel.Children.Add(new TextBlock
{
- Text = "No gateways", FontSize = 11, Foreground = DimTextBrush
+ Text = LocalizationHelper.GetString("ConnectionStatus_NoGateways"), FontSize = 11, Foreground = DimTextBrush
});
return;
}
@@ -301,13 +301,13 @@ private async Task OnConnectAsync()
if (!string.IsNullOrEmpty(code))
{
ConnectButton.IsEnabled = false;
- SetupCodeResult.Text = "Applying…";
+ SetupCodeResult.Text = LocalizationHelper.GetString("ConnectionStatus_Applying");
try
{
var result = await _manager.ApplySetupCodeAsync(code);
SetupCodeResult.Text = result.Outcome switch
{
- SetupCodeOutcome.Success => $"✓ Connected to {GatewayUrlHelper.SanitizeForDisplay(result.GatewayUrl ?? "")}",
+ SetupCodeOutcome.Success => string.Format(LocalizationHelper.GetString("ConnectionStatus_ConnectedTo"), GatewayUrlHelper.SanitizeForDisplay(result.GatewayUrl ?? "")),
_ => $"✗ {result.ErrorMessage ?? result.Outcome.ToString()}"
};
}
@@ -319,7 +319,7 @@ private async Task OnConnectAsync()
else
{
// Reconnect to active gateway
- SetupCodeResult.Text = "Reconnecting…";
+ SetupCodeResult.Text = LocalizationHelper.GetString("ConnectionStatus_Reconnecting");
await _manager.ReconnectAsync();
SetupCodeResult.Text = "";
}
@@ -335,7 +335,7 @@ private async Task OnDisconnectClickAsync()
{
if (_manager == null) return;
await _manager.DisconnectAsync();
- SetupCodeResult.Text = "Disconnected";
+ SetupCodeResult.Text = LocalizationHelper.GetString("ConnectionStatus_Disconnected");
}
private void OnDiagSshToggled(object sender, RoutedEventArgs e)
@@ -357,7 +357,7 @@ private async Task OnDirectConnectAsync()
var token = DirectTokenBox.Text?.Trim();
if (string.IsNullOrWhiteSpace(url))
{
- DirectConnectResult.Text = "Enter a gateway URL";
+ DirectConnectResult.Text = LocalizationHelper.GetString("ConnectionStatus_EnterGatewayUrl");
return;
}
@@ -377,7 +377,7 @@ private async Task OnDirectConnectAsync()
sshConfig = new SshTunnelConfig(sshUser, sshHost, remotePort, localPort);
}
- DirectConnectResult.Text = useSsh ? "Starting SSH tunnel…" : "Connecting…";
+ DirectConnectResult.Text = useSsh ? LocalizationHelper.GetString("ConnectionStatus_StartingSshTunnel") : LocalizationHelper.GetString("ConnectionStatus_Connecting");
try
{
await _manager.DisconnectAsync();
@@ -414,11 +414,11 @@ private async Task OnDirectConnectAsync()
settings.SshTunnelLocalPort = sshConfig.LocalPort;
settings.Save();
app.EnsureSshTunnelStarted();
- DirectConnectResult.Text = "Connecting…";
+ DirectConnectResult.Text = LocalizationHelper.GetString("ConnectionStatus_Connecting");
}
await _manager.ConnectAsync(recordId);
- DirectConnectResult.Text = $"✓ Connected to {GatewayUrlHelper.SanitizeForDisplay(url)}";
+ DirectConnectResult.Text = string.Format(LocalizationHelper.GetString("ConnectionStatus_ConnectedTo"), GatewayUrlHelper.SanitizeForDisplay(url));
}
catch (Exception ex)
{
diff --git a/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml b/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml
index bc75f9e4..2a757d2a 100644
--- a/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml
+++ b/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml
@@ -77,7 +77,7 @@
Tapped="OnTitleBarStatusTapped">
-
diff --git a/tests/OpenClaw.Tray.Tests/LocalizationValidationTests.cs b/tests/OpenClaw.Tray.Tests/LocalizationValidationTests.cs
index f52eae38..b3892f24 100644
--- a/tests/OpenClaw.Tray.Tests/LocalizationValidationTests.cs
+++ b/tests/OpenClaw.Tray.Tests/LocalizationValidationTests.cs
@@ -47,6 +47,8 @@ public class LocalizationValidationTests
// VoiceOverlayWindow window-title key — matches the convention
// for ChatWindow / HubWindow / CanvasWindow / TrayMenuWindow.
"VoiceOverlayWindow_winexWindowEx_2.Title",
+ // Brand name — identical across all locales.
+ "ConnectionPage_TopologyTailscale",
"PermissionsPage_TtsElevenLabsModel.PlaceholderText",
"PermissionsPage_TtsProviderElevenLabs.Content",
// Sample IDs / brand identifiers — same across locales.
@@ -430,6 +432,8 @@ public void MojibakeDetector_AllowsLegitimateUnicode()
{
"Update_OK",
"Onboarding_IncompleteSetup_Close",
+ "ChatPage_OK",
+ "ConnectionPage_ViaSSH",
};
// Locales whose translations are allowed to remain identical to en-us