From 9cf91d461c89c7dbc69d9034cc8c2b15d71299a1 Mon Sep 17 00:00:00 2001 From: Christine Yan Date: Tue, 26 May 2026 15:07:23 -0400 Subject: [PATCH 1/5] Localize 58 hardcoded XAML strings in CronPage.xaml Add x:Uid attributes to all 57 elements in CronPage.xaml that had hardcoded Text, Content, PlaceholderText, Title, or Message properties. The corresponding .resw entries already exist in all 5 locale files (en-us, fr-fr, nl-nl, zh-cn, zh-tw). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml | 118 ++++++++++---------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml index 1e422fe25..cd663c4c6 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml @@ -1,4 +1,4 @@ - + - + @@ -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 fbaf90fdc..5175b2391 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 e062ea61d..dd146e2e8 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs @@ -778,7 +778,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; } @@ -818,7 +818,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 @@ -864,23 +864,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 { @@ -901,12 +901,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), }; @@ -925,13 +925,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), }; @@ -949,7 +949,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) @@ -1060,14 +1060,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), }; @@ -1079,67 +1079,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"), }), }; @@ -1169,65 +1166,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, }; @@ -1338,12 +1335,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); @@ -1360,8 +1357,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; @@ -1380,8 +1377,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; @@ -1396,8 +1393,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(); @@ -1408,8 +1405,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; @@ -1425,8 +1422,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; @@ -1441,7 +1438,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(); @@ -1452,30 +1449,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 @@ -1932,15 +1928,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 3393810c4..d2a2aabc9 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; } @@ -676,10 +676,10 @@ private void OnRetryChat(object sender, RoutedEventArgs e) { var dialog = new ContentDialog { - Title = "Voice Model Required", - Content = "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?", - PrimaryButtonText = "Open Voice Settings", - CloseButtonText = "Cancel", + Title = LocalizationHelper.GetString("ChatPage_VoiceModelRequired"), + Content = LocalizationHelper.GetString("ChatPage_VoiceModelInstallMessage"), + PrimaryButtonText = LocalizationHelper.GetString("ChatPage_OpenVoiceSettings"), + CloseButtonText = LocalizationHelper.GetString("ChatPage_Cancel"), XamlRoot = Content?.XamlRoot }; // Light-dismiss: close when the user taps the smoke overlay. @@ -753,7 +753,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) { @@ -782,9 +782,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.cs b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs index 8bf08fd41..45bf5454e 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs @@ -481,14 +481,14 @@ private void ApplyGlanceChips(ConnectionPagePlan plan, bool show) // tray (and therefore Operator + Node) runs on. Free signal. if (!string.IsNullOrWhiteSpace(hostname)) { - chips.Add(BuildGlanceChip(Helpers.FluentIconCatalog.Hostname, $"on {hostname}", neutral: true)); + chips.Add(BuildGlanceChip(Helpers.FluentIconCatalog.Hostname, string.Format(LocalizationHelper.GetString("ConnectionPage_OnHostname"), hostname), neutral: true)); } // 3. Presence count — "1 client" / "3 clients" if the gateway has // multiple operators connected. if (presence is int n && n > 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)); } @@ -496,7 +496,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)); } @@ -505,16 +507,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!); @@ -535,28 +537,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 { @@ -622,25 +624,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); @@ -675,11 +677,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 @@ -697,32 +699,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); @@ -792,41 +794,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"), }, }; @@ -837,7 +839,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) { @@ -957,8 +959,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)); } @@ -1028,17 +1032,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) @@ -1078,16 +1082,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 @@ -1181,7 +1185,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"), }); @@ -1219,7 +1223,7 @@ private Border BuildSavedGatewayRowControl(SavedGatewayRow row) { var connectBtn = new Button { - Content = "Connect", + Content = LocalizationHelper.GetString("ConnectionPage_ConnectAction"), Tag = row.Id, VerticalAlignment = VerticalAlignment.Center, }; @@ -1250,7 +1254,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 (hasLiveAffordance) @@ -1262,23 +1266,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); } @@ -1294,12 +1298,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 @@ -1363,7 +1367,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); @@ -1469,11 +1473,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); } } @@ -1511,8 +1515,8 @@ private async void OnApplyRepairCode(object sender, RoutedEventArgs e) { 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) { @@ -1546,7 +1550,7 @@ private async void OnConnectSavedGateway(object sender, RoutedEventArgs e) // 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; @@ -1610,10 +1614,10 @@ private async void OnSavedRowRemove(object sender, RoutedEventArgs e) 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, }; @@ -1693,7 +1697,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 { @@ -1719,17 +1723,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"]; } } @@ -1754,7 +1758,7 @@ private async void OnAddSave(object sender, RoutedEventArgs e) { 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) @@ -1779,7 +1783,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; } @@ -1796,19 +1800,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; @@ -1911,15 +1915,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; @@ -1974,7 +1978,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); } @@ -1995,7 +1999,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; @@ -2070,12 +2074,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) @@ -2083,11 +2087,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) { @@ -2110,7 +2114,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(); } } @@ -2136,11 +2140,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)) @@ -2194,10 +2198,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(); @@ -2209,8 +2213,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; @@ -2222,15 +2226,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"); } } @@ -2279,7 +2283,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); @@ -2514,8 +2518,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); } /// @@ -2638,10 +2642,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 { @@ -2673,14 +2677,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)) { @@ -2731,10 +2735,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 { @@ -2746,7 +2750,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"), }); @@ -2765,14 +2769,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)) { @@ -2817,14 +2821,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 cd663c4c6..1a900d8d6 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml @@ -52,7 +52,7 @@ - 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 => @@ -194,8 +195,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; } @@ -209,8 +210,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 4a0f3aee3..7a5fc4e3a 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 7aa4124bc..456d82b6a 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"/> - @@ -211,7 +211,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 9d4a9a5d6..c0ec58d18 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw @@ -4577,4 +4577,846 @@ 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… + + diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index ef2a53b63..3966c1798 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw @@ -4529,4 +4529,846 @@ 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… + + diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index 148b26e27..845de2b93 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw @@ -4530,4 +4530,846 @@ 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… + + diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw index f363ac4b2..cffa60364 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw @@ -4529,4 +4529,846 @@ 高级 - + + + 实时 + + + 清除 + + + æ­¤ç½‘å…³å½“å‰æ­£åœ¨å¤„ç†çš„对è¯ï¼ŒæŒ‰é¢‘é“分组。 + + + 当频é“已连接且有æµé‡æ—¶ï¼Œä¼šè¯å°†æ˜¾ç¤ºåœ¨æ­¤å¤„。 + + + 打开èŠå¤© + + + å·²å¯ç”¨ + + + å·²ç¦ç”¨ + + + 预览 + + + 语音预览 + + + 已断开连接 + + + 代ç†ç›´æŽ¥åœ¨ç½‘关上è¿è¡Œçš„命令ä¸å—æ­¤é™åˆ¶ã€‚如果网关在此电脑上,它们ä»å¯èƒ½è®¿é—®æ‚¨çš„ 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} é…置已ä¿å­˜ + + + 正在等待网关确认频é“å·²è¿è¡Œâ€¦ + + diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw index fc36d5e96..0a61ebb4f 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw @@ -4529,4 +4529,846 @@ 進階 - + + + 峿™‚ + + + 清除 + + + 此閘é“ç›®å‰æ­£åœ¨è™•ç†çš„å°è©±ï¼Œä¾é »é“分組。 + + + ç•¶é »é“已連線且有æµé‡æ™‚,工作階段將顯示在此處。 + + + 開啟èŠå¤© + + + 已啟用 + + + å·²åœç”¨ + + + é è¦½ + + + 語音é è¦½ + + + 已中斷連線 + + + 代ç†ç›´æŽ¥åœ¨é–˜é“上執行的命令ä¸å—æ­¤é™åˆ¶ã€‚如果閘é“在此電腦上,它們ä»å¯èƒ½å­˜å–您的 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} 設定已儲存 + + + 正在等待閘é“確èªé »é“已執行… + + diff --git a/src/OpenClaw.Tray.WinUI/Windows/ConnectionStatusWindow.xaml.cs b/src/OpenClaw.Tray.WinUI/Windows/ConnectionStatusWindow.xaml.cs index df38b0577..a9972c467 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; } @@ -295,13 +295,13 @@ private async void OnConnect(object sender, RoutedEventArgs e) 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()}" }; } @@ -313,7 +313,7 @@ private async void OnConnect(object sender, RoutedEventArgs e) else { // Reconnect to active gateway - SetupCodeResult.Text = "Reconnecting…"; + SetupCodeResult.Text = LocalizationHelper.GetString("ConnectionStatus_Reconnecting"); await _manager.ReconnectAsync(); SetupCodeResult.Text = ""; } @@ -323,7 +323,7 @@ private async void OnDisconnectClick(object sender, RoutedEventArgs e) { if (_manager == null) return; await _manager.DisconnectAsync(); - SetupCodeResult.Text = "Disconnected"; + SetupCodeResult.Text = LocalizationHelper.GetString("ConnectionStatus_Disconnected"); } private void OnDiagSshToggled(object sender, RoutedEventArgs e) @@ -339,7 +339,7 @@ private async void OnDirectConnect(object sender, RoutedEventArgs e) var token = DirectTokenBox.Text?.Trim(); if (string.IsNullOrWhiteSpace(url)) { - DirectConnectResult.Text = "Enter a gateway URL"; + DirectConnectResult.Text = LocalizationHelper.GetString("ConnectionStatus_EnterGatewayUrl"); return; } @@ -359,7 +359,7 @@ private async void OnDirectConnect(object sender, RoutedEventArgs e) 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(); @@ -396,11 +396,11 @@ private async void OnDirectConnect(object sender, RoutedEventArgs e) 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 bc75f9e47..2a757d2a8 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 aa012f1a3..ea92119e5 100644 --- a/tests/OpenClaw.Tray.Tests/LocalizationValidationTests.cs +++ b/tests/OpenClaw.Tray.Tests/LocalizationValidationTests.cs @@ -45,6 +45,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. @@ -267,6 +269,8 @@ private static bool IsInvariantValue(string value) => { "Update_OK", "Onboarding_IncompleteSetup_Close", + "ChatPage_OK", + "ConnectionPage_ViaSSH", }; // Locales whose translations are allowed to remain identical to en-us From e30483c2f3b3b51b26faa9564d9e0ce8212e410a Mon Sep 17 00:00:00 2001 From: Christine Yan Date: Wed, 3 Jun 2026 16:05:53 -0400 Subject: [PATCH 3/5] Localize gateway terminal label --- src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw | 2 +- src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index 2d27f0c18..8a23b2e9a 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw @@ -5426,7 +5426,7 @@ Vérifiez vos paramètres de connexion et réessayez. Ouvrez un shell ou gérez le service de passerelle locale dans WSL. - Terminal + Invite de commandes Démarrer diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index 94ec70cd2..df3cb0964 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw @@ -5427,7 +5427,7 @@ Controleer uw verbindingsinstellingen en probeer het opnieuw. Open een shell of beheer de lokale gatewayservice in WSL. - Terminal + Terminalvenster Starten From 69ae1bf9997da375f45a1ac37832336f1fc873bb Mon Sep 17 00:00:00 2001 From: Christine Yan Date: Wed, 3 Jun 2026 16:31:10 -0400 Subject: [PATCH 4/5] Localize Cron sidebar label --- src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw | 2 +- src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw | 2 +- src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw | 2 +- src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index 8a23b2e9a..f1b50c19b 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 diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index df3cb0964..bae742e7c 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 diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw index 4071ea285..bc65ced21 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 + 计划任务 此计算机 diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw index 7e946cdc1..ae580a6cb 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 + 排程工作 這部電腦 From c37fd1ad066e5affccbfbf395aa6aeb66e6bc8d7 Mon Sep 17 00:00:00 2001 From: Christine Yan Date: Wed, 3 Jun 2026 17:05:47 -0400 Subject: [PATCH 5/5] Rename Cron schedule option resource UIDs --- src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml | 7 +++---- src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw | 6 +++--- src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw | 6 +++--- src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw | 6 +++--- src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw | 6 +++--- src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw | 6 +++--- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml index 1a900d8d6..dbb453608 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml @@ -97,9 +97,9 @@ - - - + + + @@ -293,4 +293,3 @@ - diff --git a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw index f49e3e235..7740514b7 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 diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index f1b50c19b..94d9d8131 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw @@ -3906,13 +3906,13 @@ Sur votre hôte passerelle (Mac/Linux), exécutez : PLANIFICATION - + Toutes les - + À - + Cron diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index bae742e7c..c66316604 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw @@ -3907,13 +3907,13 @@ Voer op uw gateway-host (Mac/Linux) uit: PLANNING - + Elke - + Om - + Cron diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw index bc65ced21..106bb16a2 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw @@ -3906,13 +3906,13 @@ 计划 - + æ¯ - + 于 - + Cron diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw index ae580a6cb..e22f5630d 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw @@ -3906,13 +3906,13 @@ 排程 - + æ¯ - + æ–¼ - + Cron