feat(i18n): localize installer with language picker and config write-through

- Init Localizer at app startup (before self-update prompt) and assign to TrExtension.Localizer
- Register ILocalizer in DI; inject into WizardViewModel and SettingsViewModel
- WizardViewModel: SelectedLanguage ComboBox binding with OnSelectedLanguageChanged -> SetLanguage + InstallContext.Language
- WizardWindow.xaml: DockPanel wraps step chips + language ComboBox (right-aligned)
- Localize all installer XAML: WizardWindow, WelcomePage, PathsPage, ServicePage, UiSettingsPage, InstallPage, SettingsWindow, SelfUpdatePromptWindow
- Localize page Title properties and WizardViewModel.NextButtonText via TrExtension.Localizer
- Persist chosen Language in WriteConfigStep and SettingsViewModel.Save into ui.config.json
- Append installer section to en.json (nav, welcome, paths, service, uiSettings, install, settings, selfUpdate)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-03 12:55:08 +02:00
parent 2fbf054a57
commit 364a037cb3
19 changed files with 210 additions and 83 deletions

View File

@@ -1,7 +1,12 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Windows; using System.Windows;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using ClaudeDo.Localization;
using ClaudeDo.Releases; using ClaudeDo.Releases;
using ClaudeDo.Installer.Pages.InstallPage; using ClaudeDo.Installer.Pages.InstallPage;
using ClaudeDo.Installer.Pages.PathsPage; using ClaudeDo.Installer.Pages.PathsPage;
@@ -22,6 +27,17 @@ public partial class App : Application
{ {
base.OnStartup(e); base.OnStartup(e);
// --- Initialize localizer as early as possible so all windows can use {loc:Tr} ---
var localesDir = Path.Combine(AppContext.BaseDirectory, "locales");
var localeStore = LocaleStore.Load(localesDir);
var existingSettings = InstallerAppSettings.Load();
var initialLang = !string.IsNullOrWhiteSpace(existingSettings.Language)
? existingSettings.Language
: CultureResolver.Resolve(CultureInfo.CurrentUICulture.Name,
localeStore.Available.Select(l => l.Code).ToArray(), "en");
var localizer = new Localizer(localeStore, initialLang);
TrExtension.Localizer = localizer;
// --- Self-update pre-flight --- // --- Self-update pre-flight ---
// Resolve current exe path. Assembly.Location may point to a .dll for apphost-based // Resolve current exe path. Assembly.Location may point to a .dll for apphost-based
// .NET apps; swap to the .exe companion when that happens. // .NET apps; swap to the .exe companion when that happens.
@@ -120,7 +136,7 @@ public partial class App : Application
// --- Existing wizard start-up unchanged below this line --- // --- Existing wizard start-up unchanged below this line ---
_services = BuildServices(); _services = BuildServices(localizer);
var context = _services.GetRequiredService<InstallContext>(); var context = _services.GetRequiredService<InstallContext>();
context.InstallerVersion = GetInstallerVersion(); context.InstallerVersion = GetInstallerVersion();
@@ -183,9 +199,10 @@ public partial class App : Application
return infoAttr?.InformationalVersion ?? "0.0.0"; return infoAttr?.InformationalVersion ?? "0.0.0";
} }
private static ServiceProvider BuildServices() private static ServiceProvider BuildServices(ILocalizer localizer)
{ {
var sc = new ServiceCollection(); var sc = new ServiceCollection();
sc.AddSingleton(localizer);
// Core // Core
sc.AddSingleton<InstallContext>(); sc.AddSingleton<InstallContext>();

View File

@@ -36,4 +36,7 @@ public sealed class InstallContext
// WelcomePage — register the external MCP endpoint with the Claude CLI. // WelcomePage — register the external MCP endpoint with the Claude CLI.
public bool RegisterMcpWithClaude { get; set; } = true; public bool RegisterMcpWithClaude { get; set; } = true;
public int ExternalMcpPort { get; set; } = 47_822; public int ExternalMcpPort { get; set; } = 47_822;
// Language selection (persisted to ui.config.json)
public string Language { get; set; } = "";
} }

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.InstallPage" xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.InstallPage"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
d:DataContext="{d:DesignInstance local:InstallPageViewModel}" d:DataContext="{d:DesignInstance local:InstallPageViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -17,8 +18,8 @@
<!-- Header --> <!-- Header -->
<StackPanel Grid.Row="0" Margin="0,0,0,16"> <StackPanel Grid.Row="0" Margin="0,0,0,16">
<TextBlock Text="Installation" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="{loc:Tr installer.install.title}" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/>
<TextBlock Text="Click Install to build and deploy ClaudeDo." <TextBlock Text="{loc:Tr installer.install.subtitle}"
Foreground="{StaticResource TextSecondaryBrush}" TextWrapping="Wrap"/> Foreground="{StaticResource TextSecondaryBrush}" TextWrapping="Wrap"/>
</StackPanel> </StackPanel>
@@ -89,11 +90,11 @@
<!-- Action buttons --> <!-- Action buttons -->
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0"> <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button Content="Cancel" Command="{Binding CancelInstallCommand}" <Button Content="{loc:Tr installer.nav.cancel}" Command="{Binding CancelInstallCommand}"
Visibility="{Binding IsInstalling, Converter={StaticResource BoolToVisConverter}}" Visibility="{Binding IsInstalling, Converter={StaticResource BoolToVisConverter}}"
Margin="0,0,8,0"/> Margin="0,0,8,0"/>
<Button Content="Launch ClaudeDo" Command="{Binding LaunchAppCommand}" <Button Content="{loc:Tr installer.install.launch}" Command="{Binding LaunchAppCommand}"
Style="{StaticResource AccentButton}" Style="{StaticResource AccentButton}"
Visibility="{Binding IsComplete, Converter={StaticResource BoolToVisConverter}}"/> Visibility="{Binding IsComplete, Converter={StaticResource BoolToVisConverter}}"/>
</StackPanel> </StackPanel>

View File

@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using ClaudeDo.Installer.Steps; using ClaudeDo.Installer.Steps;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@@ -29,7 +30,7 @@ public partial class InstallPageViewModel : ObservableObject, IInstallerPage
private InstallPageView? _view; private InstallPageView? _view;
private CancellationTokenSource? _cts; private CancellationTokenSource? _cts;
public string Title => "Install"; public string Title => TrExtension.Localizer?["installer.install.title"] ?? "Install";
public string Icon => "\uE896"; public string Icon => "\uE896";
public int Order => 99; public int Order => 99;
public bool ShowInWizard => true; public bool ShowInWizard => true;

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.PathsPage" xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.PathsPage"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
d:DataContext="{d:DesignInstance local:PathsPageViewModel}" d:DataContext="{d:DesignInstance local:PathsPageViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -9,28 +10,28 @@
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel MaxWidth="520"> <StackPanel MaxWidth="520">
<TextBlock Text="Data Paths" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="{loc:Tr installer.paths.title}" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/>
<TextBlock Text="Configure where ClaudeDo stores its data." <TextBlock Text="{loc:Tr installer.paths.subtitle}"
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,20" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,20"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<Label Content="Database Path"/> <Label Content="{loc:Tr installer.paths.databasePath}"/>
<TextBox Text="{Binding DbPath, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding DbPath, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<Label Content="Log Directory"/> <Label Content="{loc:Tr installer.paths.logDirectory}"/>
<TextBox Text="{Binding LogRoot, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding LogRoot, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<Label Content="Sandbox Root"/> <Label Content="{loc:Tr installer.paths.sandboxRoot}"/>
<TextBox Text="{Binding SandboxRoot, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding SandboxRoot, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<Label Content="Worktree Strategy"/> <Label Content="{loc:Tr installer.paths.worktreeStrategy}"/>
<ComboBox SelectedItem="{Binding WorktreeRootStrategy}" Margin="0,0,0,12"> <ComboBox SelectedItem="{Binding WorktreeRootStrategy}" Margin="0,0,0,12">
<sys:String xmlns:sys="clr-namespace:System;assembly=mscorlib">sibling</sys:String> <sys:String xmlns:sys="clr-namespace:System;assembly=mscorlib">sibling</sys:String>
<sys:String xmlns:sys="clr-namespace:System;assembly=mscorlib">central</sys:String> <sys:String xmlns:sys="clr-namespace:System;assembly=mscorlib">central</sys:String>
</ComboBox> </ComboBox>
<StackPanel Visibility="{Binding IsCentralVisible, Converter={StaticResource BoolToVisConverter}}"> <StackPanel Visibility="{Binding IsCentralVisible, Converter={StaticResource BoolToVisConverter}}">
<Label Content="Central Worktree Root"/> <Label Content="{loc:Tr installer.paths.centralWorktreeRoot}"/>
<TextBox Text="{Binding CentralWorktreeRoot, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding CentralWorktreeRoot, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
</StackPanel> </StackPanel>

View File

@@ -1,5 +1,6 @@
using System.Windows.Controls; using System.Windows.Controls;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace ClaudeDo.Installer.Pages.PathsPage; namespace ClaudeDo.Installer.Pages.PathsPage;
@@ -9,7 +10,7 @@ public partial class PathsPageViewModel : ObservableObject, IInstallerPage
private readonly InstallContext _context; private readonly InstallContext _context;
private PathsPageView? _view; private PathsPageView? _view;
public string Title => "Paths"; public string Title => TrExtension.Localizer?["installer.paths.title"] ?? "Paths";
public string Icon => "\uE8B7"; public string Icon => "\uE8B7";
public int Order => 1; public int Order => 1;
public bool ShowInWizard => true; public bool ShowInWizard => true;

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.ServicePage" xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.ServicePage"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
d:DataContext="{d:DesignInstance local:ServicePageViewModel}" d:DataContext="{d:DesignInstance local:ServicePageViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -9,37 +10,37 @@
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel MaxWidth="520"> <StackPanel MaxWidth="520">
<TextBlock Text="Worker" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="{loc:Tr installer.service.title}" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/>
<TextBlock Text="Configure the ClaudeDo background worker." <TextBlock Text="{loc:Tr installer.service.subtitle}"
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,20" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,20"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<Label Content="SignalR Port"/> <Label Content="{loc:Tr installer.service.signalRPort}"/>
<TextBox Text="{Binding SignalRPort, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding SignalRPort, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<Label Content="Queue Backstop Interval (ms)"/> <Label Content="{loc:Tr installer.service.queueBackstopInterval}"/>
<TextBox Text="{Binding QueueBackstopIntervalMs, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding QueueBackstopIntervalMs, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<Label Content="Claude CLI Path"/> <Label Content="{loc:Tr installer.service.claudeCliPath}"/>
<Grid Margin="0,0,0,12"> <Grid Margin="0,0,0,12">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ClaudeBin, UpdateSourceTrigger=PropertyChanged}"/> <TextBox Grid.Column="0" Text="{Binding ClaudeBin, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Content="Browse..." Command="{Binding BrowseClaudeCommand}" <Button Grid.Column="1" Content="{loc:Tr installer.nav.browse}" Command="{Binding BrowseClaudeCommand}"
Margin="8,0,0,0"/> Margin="8,0,0,0"/>
</Grid> </Grid>
<Separator Margin="0,4,0,12"/> <Separator Margin="0,4,0,12"/>
<TextBlock Text="The worker runs as you (the logged-in user) via a per-user logon task, so it can use your Claude CLI authentication." <TextBlock Text="{loc:Tr installer.service.autostartHint}"
Foreground="{StaticResource TextDimBrush}" FontSize="11" Margin="0,0,0,12" Foreground="{StaticResource TextDimBrush}" FontSize="11" Margin="0,0,0,12"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<CheckBox Content="Start worker automatically at logon" IsChecked="{Binding AutoStart}" Margin="0,0,0,12"/> <CheckBox Content="{loc:Tr installer.service.autostart}" IsChecked="{Binding AutoStart}" Margin="0,0,0,12"/>
<Label Content="Restart Delay (ms)"/> <Label Content="{loc:Tr installer.service.restartDelay}"/>
<TextBox Text="{Binding RestartDelayMs, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/> <TextBox Text="{Binding RestartDelayMs, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<TextBlock Text="{Binding ValidationError}" Foreground="{StaticResource ErrorBrush}" FontSize="11" <TextBlock Text="{Binding ValidationError}" Foreground="{StaticResource ErrorBrush}" FontSize="11"

View File

@@ -1,5 +1,6 @@
using System.Windows.Controls; using System.Windows.Controls;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Microsoft.Win32; using Microsoft.Win32;
@@ -11,7 +12,7 @@ public partial class ServicePageViewModel : ObservableObject, IInstallerPage
private readonly InstallContext _context; private readonly InstallContext _context;
private ServicePageView? _view; private ServicePageView? _view;
public string Title => "Service"; public string Title => TrExtension.Localizer?["installer.service.title"] ?? "Service";
public string Icon => "\uE912"; public string Icon => "\uE912";
public int Order => 2; public int Order => 2;
public bool ShowInWizard => true; public bool ShowInWizard => true;

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.UiSettingsPage" xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.UiSettingsPage"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
d:DataContext="{d:DesignInstance local:UiSettingsPageViewModel}" d:DataContext="{d:DesignInstance local:UiSettingsPageViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -9,22 +10,22 @@
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel MaxWidth="520"> <StackPanel MaxWidth="520">
<TextBlock Text="UI Settings" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="{loc:Tr installer.uiSettings.title}" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,4"/>
<TextBlock Text="Configure the ClaudeDo desktop UI connection settings." <TextBlock Text="{loc:Tr installer.uiSettings.subtitle}"
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,20" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,20"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<CheckBox Content="Sync with service settings" IsChecked="{Binding IsSynced}" Margin="0,0,0,16"/> <CheckBox Content="{loc:Tr installer.uiSettings.syncWithService}" IsChecked="{Binding IsSynced}" Margin="0,0,0,16"/>
<Label Content="SignalR URL"/> <Label Content="{loc:Tr installer.uiSettings.signalRUrl}"/>
<TextBox Text="{Binding SignalRUrl, UpdateSourceTrigger=PropertyChanged}" <TextBox Text="{Binding SignalRUrl, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="{Binding IsSynced}" Margin="0,0,0,12"/> IsReadOnly="{Binding IsSynced}" Margin="0,0,0,12"/>
<Label Content="Database Path"/> <Label Content="{loc:Tr installer.paths.databasePath}"/>
<TextBox Text="{Binding UiDbPath, UpdateSourceTrigger=PropertyChanged}" <TextBox Text="{Binding UiDbPath, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="{Binding IsSynced}" Margin="0,0,0,12"/> IsReadOnly="{Binding IsSynced}" Margin="0,0,0,12"/>
<TextBlock Text="When synced, these values are derived from the Service and Paths pages." <TextBlock Text="{loc:Tr installer.uiSettings.syncHint}"
Foreground="{StaticResource TextDimBrush}" FontSize="11" TextWrapping="Wrap" Foreground="{StaticResource TextDimBrush}" FontSize="11" TextWrapping="Wrap"
Visibility="{Binding IsSynced, Converter={StaticResource BoolToVisConverter}}" Visibility="{Binding IsSynced, Converter={StaticResource BoolToVisConverter}}"
Margin="0,0,0,12"/> Margin="0,0,0,12"/>

View File

@@ -1,5 +1,6 @@
using System.Windows.Controls; using System.Windows.Controls;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace ClaudeDo.Installer.Pages.UiSettingsPage; namespace ClaudeDo.Installer.Pages.UiSettingsPage;
@@ -9,7 +10,7 @@ public partial class UiSettingsPageViewModel : ObservableObject, IInstallerPage
private readonly InstallContext _context; private readonly InstallContext _context;
private UiSettingsPageView? _view; private UiSettingsPageView? _view;
public string Title => "UI Settings"; public string Title => TrExtension.Localizer?["installer.uiSettings.title"] ?? "UI Settings";
public string Icon => "\uE771"; public string Icon => "\uE771";
public int Order => 3; public int Order => 3;
public bool ShowInWizard => true; public bool ShowInWizard => true;

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.WelcomePage" xmlns:local="clr-namespace:ClaudeDo.Installer.Pages.WelcomePage"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
d:DataContext="{d:DesignInstance local:WelcomePageViewModel}" d:DataContext="{d:DesignInstance local:WelcomePageViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -14,7 +15,7 @@
<TextBlock Text="{Binding Subheading}" TextWrapping="Wrap" <TextBlock Text="{Binding Subheading}" TextWrapping="Wrap"
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,24"/> Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,24"/>
<Label Content="Install Directory"/> <Label Content="{loc:Tr installer.welcome.installDirectory}"/>
<Grid Margin="0,0,0,4"> <Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -24,7 +25,7 @@
Text="{Binding InstallDirectory, UpdateSourceTrigger=PropertyChanged}" Text="{Binding InstallDirectory, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding InstallDirEditable}"/> IsEnabled="{Binding InstallDirEditable}"/>
<Button Grid.Column="1" <Button Grid.Column="1"
Content="Browse..." Content="{loc:Tr installer.nav.browse}"
Margin="8,0,0,0" Margin="8,0,0,0"
Command="{Binding BrowseInstallCommand}" Command="{Binding BrowseInstallCommand}"
IsEnabled="{Binding InstallDirEditable}"/> IsEnabled="{Binding InstallDirEditable}"/>
@@ -32,10 +33,10 @@
<TextBlock Text="{Binding InstallError}" Foreground="{StaticResource ErrorBrush}" FontSize="11" <TextBlock Text="{Binding InstallError}" Foreground="{StaticResource ErrorBrush}" FontSize="11"
Visibility="{Binding InstallError, Converter={StaticResource NullToCollapsedConverter}}"/> Visibility="{Binding InstallError, Converter={StaticResource NullToCollapsedConverter}}"/>
<CheckBox Content="Register MCP server with Claude" <CheckBox Content="{loc:Tr installer.welcome.registerMcp}"
IsChecked="{Binding RegisterMcp}" IsChecked="{Binding RegisterMcp}"
Margin="0,24,0,0"/> Margin="0,24,0,0"/>
<TextBlock Text="Runs 'claude mcp add' so Claude can view and manage your ClaudeDo tasks. You can change this later." <TextBlock Text="{loc:Tr installer.welcome.registerMcpHint}"
TextWrapping="Wrap" FontSize="11" TextWrapping="Wrap" FontSize="11"
Foreground="{StaticResource TextSecondaryBrush}" Foreground="{StaticResource TextSecondaryBrush}"
Margin="0,4,0,0"/> Margin="0,4,0,0"/>

View File

@@ -1,6 +1,7 @@
using System.IO; using System.IO;
using System.Windows.Controls; using System.Windows.Controls;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Microsoft.Win32; using Microsoft.Win32;
@@ -12,7 +13,7 @@ public partial class WelcomePageViewModel : ObservableObject, IInstallerPage
private readonly InstallContext _context; private readonly InstallContext _context;
private WelcomePageView? _view; private WelcomePageView? _view;
public string Title => "Welcome"; public string Title => TrExtension.Localizer?["installer.welcome.title"] ?? "Welcome";
public string Icon => "\uE80F"; public string Icon => "\uE80F";
public int Order => 0; public int Order => 0;
public bool ShowInWizard => true; public bool ShowInWizard => true;
@@ -37,17 +38,18 @@ public partial class WelcomePageViewModel : ObservableObject, IInstallerPage
? @"C:\Program Files\ClaudeDo" ? @"C:\Program Files\ClaudeDo"
: _context.InstallDirectory; : _context.InstallDirectory;
var loc = TrExtension.Localizer;
switch (_context.Mode) switch (_context.Mode)
{ {
case InstallerMode.FreshInstall: case InstallerMode.FreshInstall:
Heading = "Install ClaudeDo"; Heading = loc?["installer.welcome.heading"] ?? "Install ClaudeDo";
Subheading = "Choose where to install ClaudeDo, then click Next."; Subheading = loc?["installer.welcome.subheading"] ?? "Choose where to install ClaudeDo, then click Next.";
InstallDirEditable = true; InstallDirEditable = true;
break; break;
case InstallerMode.Update: case InstallerMode.Update:
Heading = $"Update ClaudeDo {_context.InstalledVersion ?? "?"} -> {_context.LatestVersion ?? "?"}"; Heading = $"Update ClaudeDo {_context.InstalledVersion ?? "?"} -> {_context.LatestVersion ?? "?"}";
Subheading = "Your tasks, config, and database will be preserved. Click Next to continue."; Subheading = loc?["installer.welcome.updateSubheading"] ?? "Your tasks, config, and database will be preserved. Click Next to continue.";
InstallDirEditable = false; // stay where we were installed InstallDirEditable = false; // stay where we were installed
break; break;

View File

@@ -31,6 +31,7 @@ public sealed class WriteConfigStep : IInstallStep
{ {
DbPath = Paths.Expand(ctx.UiDbPath), DbPath = Paths.Expand(ctx.UiDbPath),
SignalRUrl = ctx.SignalRUrl, SignalRUrl = ctx.SignalRUrl,
Language = ctx.Language,
}; };
uiCfg.Save(); uiCfg.Save();
progress.Report("Written ui.config.json"); progress.Report("Written ui.config.json");

View File

@@ -1,6 +1,7 @@
<Window x:Class="ClaudeDo.Installer.Views.SelfUpdatePromptWindow" <Window x:Class="ClaudeDo.Installer.Views.SelfUpdatePromptWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
Title="ClaudeDo Installer Update" Title="ClaudeDo Installer Update"
Width="460" Height="200" Width="460" Height="200"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
@@ -13,13 +14,13 @@
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="16" FontWeight="SemiBold" Text="A newer installer is available"/> <TextBlock Grid.Row="0" FontSize="16" FontWeight="SemiBold" Text="{loc:Tr installer.selfUpdate.heading}"/>
<TextBlock Grid.Row="1" Margin="0,8,0,0" TextWrapping="Wrap" x:Name="DetailText"/> <TextBlock Grid.Row="1" Margin="0,8,0,0" TextWrapping="Wrap" x:Name="DetailText"/>
<TextBlock Grid.Row="2" Margin="0,12,0,0" TextWrapping="Wrap" Foreground="#a0a0a0" x:Name="ProgressText" Visibility="Collapsed"/> <TextBlock Grid.Row="2" Margin="0,12,0,0" TextWrapping="Wrap" Foreground="#a0a0a0" x:Name="ProgressText" Visibility="Collapsed"/>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="UpdateBtn" Content="Update" MinWidth="90" Margin="4,0" Padding="10,4" Click="UpdateBtn_Click" IsDefault="True"/> <Button x:Name="UpdateBtn" Content="{loc:Tr installer.selfUpdate.update}" MinWidth="90" Margin="4,0" Padding="10,4" Click="UpdateBtn_Click" IsDefault="True"/>
<Button x:Name="ContinueBtn" Content="Continue anyway" MinWidth="140" Margin="4,0" Padding="10,4" Click="ContinueBtn_Click"/> <Button x:Name="ContinueBtn" Content="{loc:Tr installer.selfUpdate.continueAnyway}" MinWidth="140" Margin="4,0" Padding="10,4" Click="ContinueBtn_Click"/>
<Button x:Name="CancelBtn" Content="Cancel" MinWidth="90" Margin="4,0" Padding="10,4" Click="CancelBtn_Click" IsCancel="True"/> <Button x:Name="CancelBtn" Content="{loc:Tr installer.nav.cancel}" MinWidth="90" Margin="4,0" Padding="10,4" Click="CancelBtn_Click" IsCancel="True"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </Window>

View File

@@ -1,5 +1,6 @@
using System.Windows; using System.Windows;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Localization;
using ClaudeDo.Releases; using ClaudeDo.Releases;
using ClaudeDo.Installer.Steps; using ClaudeDo.Installer.Steps;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
@@ -10,6 +11,7 @@ namespace ClaudeDo.Installer.Views;
public partial class SettingsViewModel : ObservableObject public partial class SettingsViewModel : ObservableObject
{ {
private readonly InstallContext _context; private readonly InstallContext _context;
private readonly ILocalizer _localizer;
private readonly IReleaseClient _releases; private readonly IReleaseClient _releases;
private readonly StopWorkerStep _stopService; private readonly StopWorkerStep _stopService;
private readonly StartWorkerStep _startService; private readonly StartWorkerStep _startService;
@@ -37,6 +39,7 @@ public partial class SettingsViewModel : ObservableObject
public SettingsViewModel( public SettingsViewModel(
PageResolver resolver, PageResolver resolver,
InstallContext context, InstallContext context,
ILocalizer localizer,
IReleaseClient releases, IReleaseClient releases,
StopWorkerStep stopService, StopWorkerStep stopService,
StartWorkerStep startService, StartWorkerStep startService,
@@ -46,6 +49,7 @@ public partial class SettingsViewModel : ObservableObject
{ {
Pages = resolver.SettingsPages; Pages = resolver.SettingsPages;
_context = context; _context = context;
_localizer = localizer;
_releases = releases; _releases = releases;
_stopService = stopService; _stopService = stopService;
_startService = startService; _startService = startService;
@@ -104,6 +108,7 @@ public partial class SettingsViewModel : ObservableObject
{ {
DbPath = _context.UiDbPath, DbPath = _context.UiDbPath,
SignalRUrl = _context.SignalRUrl, SignalRUrl = _context.SignalRUrl,
Language = _localizer.CurrentCode,
}; };
uiCfg.Save(); uiCfg.Save();

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:ClaudeDo.Installer.Views" xmlns:views="clr-namespace:ClaudeDo.Installer.Views"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
Title="ClaudeDo Settings" Title="ClaudeDo Settings"
Icon="/ClaudeTaskSetup.ico" Icon="/ClaudeTaskSetup.ico"
Width="720" Height="520" Width="720" Height="520"
@@ -90,16 +91,16 @@
</StackPanel> </StackPanel>
<CheckBox Grid.Column="1" IsChecked="{Binding RemoveAppData}" <CheckBox Grid.Column="1" IsChecked="{Binding RemoveAppData}"
Content="Remove user data (tasks, logs, configs in ~/.todo-app)" Content="{loc:Tr installer.settings.removeUserData}"
Margin="0,0,12,0" VerticalAlignment="Center"/> Margin="0,0,12,0" VerticalAlignment="Center"/>
<Button Grid.Column="2" Content="Uninstall" Margin="0,0,8,0" <Button Grid.Column="2" Content="{loc:Tr installer.settings.uninstall}" Margin="0,0,8,0"
Command="{Binding UninstallCommand}"/> Command="{Binding UninstallCommand}"/>
<Button Grid.Column="3" Content="Repair" Margin="0,0,8,0" <Button Grid.Column="3" Content="{loc:Tr installer.settings.repair}" Margin="0,0,8,0"
Command="{Binding RepairCommand}"/> Command="{Binding RepairCommand}"/>
<Button Grid.Column="4" Content="Save" Margin="0,0,8,0" <Button Grid.Column="4" Content="{loc:Tr installer.settings.save}" Margin="0,0,8,0"
Command="{Binding SaveCommand}" Command="{Binding SaveCommand}"
Style="{StaticResource AccentButton}"/> Style="{StaticResource AccentButton}"/>
<Button Grid.Column="5" Content="Close" <Button Grid.Column="5" Content="{loc:Tr installer.settings.close}"
Command="{Binding CloseCommand}"/> Command="{Binding CloseCommand}"/>
</Grid> </Grid>
</Border> </Border>

View File

@@ -3,6 +3,7 @@ using System.Windows;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Pages.InstallPage; using ClaudeDo.Installer.Pages.InstallPage;
using ClaudeDo.Installer.Pages.WelcomePage; using ClaudeDo.Installer.Pages.WelcomePage;
using ClaudeDo.Localization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@@ -11,8 +12,20 @@ namespace ClaudeDo.Installer.Views;
public partial class WizardViewModel : ObservableObject public partial class WizardViewModel : ObservableObject
{ {
private readonly InstallContext _context; private readonly InstallContext _context;
private readonly ILocalizer _localizer;
public IReadOnlyList<IInstallerPage> Pages { get; } public IReadOnlyList<IInstallerPage> Pages { get; }
public IReadOnlyList<LanguageOption> Languages { get; }
[ObservableProperty]
private LanguageOption? _selectedLanguage;
partial void OnSelectedLanguageChanged(LanguageOption? value)
{
if (value is null) return;
_localizer.SetLanguage(value.Value.Code);
_context.Language = value.Value.Code;
}
[ObservableProperty] [ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanGoBack))] [NotifyPropertyChangedFor(nameof(CanGoBack))]
@@ -24,14 +37,20 @@ public partial class WizardViewModel : ObservableObject
public IInstallerPage CurrentPage => Pages[CurrentPageIndex]; public IInstallerPage CurrentPage => Pages[CurrentPageIndex];
public bool CanGoBack => CurrentPageIndex > 0; public bool CanGoBack => CurrentPageIndex > 0;
public bool IsLastPage => CurrentPageIndex == Pages.Count - 1; public bool IsLastPage => CurrentPageIndex == Pages.Count - 1;
public string NextButtonText => IsLastPage ? "Install" : "Next \u2192"; public string NextButtonText => IsLastPage
? (_localizer["installer.nav.install"])
: (_localizer["installer.nav.next"]);
[ObservableProperty] [ObservableProperty]
private string? _validationError; private string? _validationError;
public WizardViewModel(PageResolver resolver, InstallContext context) public WizardViewModel(PageResolver resolver, InstallContext context, ILocalizer localizer)
{ {
_context = context; _context = context;
_localizer = localizer;
Languages = localizer.AvailableLanguages;
_selectedLanguage = Languages.FirstOrDefault(l => l.Code == localizer.CurrentCode);
_context.Language = localizer.CurrentCode;
var all = resolver.WizardPages; var all = resolver.WizardPages;
Pages = context.Mode == InstallerMode.Update Pages = context.Mode == InstallerMode.Update

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:ClaudeDo.Installer.Views" xmlns:views="clr-namespace:ClaudeDo.Installer.Views"
xmlns:loc="clr-namespace:ClaudeDo.Installer.Localization"
Title="ClaudeDo Installer" Title="ClaudeDo Installer"
Icon="/ClaudeTaskSetup.ico" Icon="/ClaudeTaskSetup.ico"
Width="720" Height="520" Width="720" Height="520"
@@ -27,40 +28,47 @@
<Border Grid.Row="0" Background="{StaticResource IslandBgBrush}" <Border Grid.Row="0" Background="{StaticResource IslandBgBrush}"
BorderBrush="{StaticResource BorderSubtleBrush}" BorderThickness="0,0,0,1" BorderBrush="{StaticResource BorderSubtleBrush}" BorderThickness="0,0,0,1"
Padding="20,14"> Padding="20,14">
<ItemsControl ItemsSource="{Binding Pages}"> <DockPanel>
<ItemsControl.ItemsPanel> <ComboBox DockPanel.Dock="Right"
<ItemsPanelTemplate> ItemsSource="{Binding Languages}"
<StackPanel Orientation="Horizontal"/> SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"
</ItemsPanelTemplate> DisplayMemberPath="Name"
</ItemsControl.ItemsPanel> Width="150" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<ItemsControl.ItemTemplate> <ItemsControl ItemsSource="{Binding Pages}">
<DataTemplate> <ItemsControl.ItemsPanel>
<Border x:Name="StepBorder" CornerRadius="4" Padding="10,5" Margin="0,0,6,0" <ItemsPanelTemplate>
BorderThickness="1"> <StackPanel Orientation="Horizontal"/>
<Border.Background> </ItemsPanelTemplate>
<MultiBinding Converter="{StaticResource StepActiveConverter}" ConverterParameter="Background"> </ItemsControl.ItemsPanel>
<Binding/> <ItemsControl.ItemTemplate>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.CurrentPage"/> <DataTemplate>
</MultiBinding> <Border x:Name="StepBorder" CornerRadius="4" Padding="10,5" Margin="0,0,6,0"
</Border.Background> BorderThickness="1">
<Border.BorderBrush> <Border.Background>
<MultiBinding Converter="{StaticResource StepActiveConverter}" ConverterParameter="BorderBrush"> <MultiBinding Converter="{StaticResource StepActiveConverter}" ConverterParameter="Background">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.CurrentPage"/>
</MultiBinding>
</Border.BorderBrush>
<TextBlock Text="{Binding Title}" FontSize="12">
<TextBlock.Foreground>
<MultiBinding Converter="{StaticResource StepActiveConverter}" ConverterParameter="Foreground">
<Binding/> <Binding/>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.CurrentPage"/> <Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.CurrentPage"/>
</MultiBinding> </MultiBinding>
</TextBlock.Foreground> </Border.Background>
</TextBlock> <Border.BorderBrush>
</Border> <MultiBinding Converter="{StaticResource StepActiveConverter}" ConverterParameter="BorderBrush">
</DataTemplate> <Binding/>
</ItemsControl.ItemTemplate> <Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.CurrentPage"/>
</ItemsControl> </MultiBinding>
</Border.BorderBrush>
<TextBlock Text="{Binding Title}" FontSize="12">
<TextBlock.Foreground>
<MultiBinding Converter="{StaticResource StepActiveConverter}" ConverterParameter="Foreground">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.CurrentPage"/>
</MultiBinding>
</TextBlock.Foreground>
</TextBlock>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Border> </Border>
<!-- Page Content --> <!-- Page Content -->
@@ -85,7 +93,7 @@
VerticalAlignment="Center" FontSize="12" VerticalAlignment="Center" FontSize="12"
Visibility="{Binding ValidationError, Converter={StaticResource NullToCollapsedConverter}}"/> Visibility="{Binding ValidationError, Converter={StaticResource NullToCollapsedConverter}}"/>
<Button Grid.Column="1" Content="Back" <Button Grid.Column="1" Content="{loc:Tr installer.nav.back}"
Command="{Binding GoBackCommand}" Command="{Binding GoBackCommand}"
IsEnabled="{Binding CanGoBack}" IsEnabled="{Binding CanGoBack}"
Margin="0,0,8,0" MinWidth="80"/> Margin="0,0,8,0" MinWidth="80"/>

View File

@@ -262,6 +262,67 @@
"emptyStateHint": "No report for this range yet. Click “Generate”." "emptyStateHint": "No report for this range yet. Click “Generate”."
} }
}, },
"installer": {
"nav": {
"back": "Back",
"next": "Next →",
"install": "Install",
"browse": "Browse...",
"cancel": "Cancel"
},
"welcome": {
"title": "Welcome",
"heading": "Install ClaudeDo",
"subheading": "Choose where to install ClaudeDo, then click Next.",
"updateSubheading": "Your tasks, config, and database will be preserved. Click Next to continue.",
"installDirectory": "Install Directory",
"registerMcp": "Register MCP server with Claude",
"registerMcpHint": "Runs 'claude mcp add' so Claude can view and manage your ClaudeDo tasks. You can change this later."
},
"paths": {
"title": "Data Paths",
"subtitle": "Configure where ClaudeDo stores its data.",
"databasePath": "Database Path",
"logDirectory": "Log Directory",
"sandboxRoot": "Sandbox Root",
"worktreeStrategy": "Worktree Strategy",
"centralWorktreeRoot": "Central Worktree Root"
},
"service": {
"title": "Worker",
"subtitle": "Configure the ClaudeDo background worker.",
"signalRPort": "SignalR Port",
"queueBackstopInterval": "Queue Backstop Interval (ms)",
"claudeCliPath": "Claude CLI Path",
"autostart": "Start worker automatically at logon",
"autostartHint": "The worker runs as you (the logged-in user) via a per-user logon task, so it can use your Claude CLI authentication.",
"restartDelay": "Restart Delay (ms)"
},
"uiSettings": {
"title": "UI Settings",
"subtitle": "Configure the ClaudeDo desktop UI connection settings.",
"syncWithService": "Sync with service settings",
"signalRUrl": "SignalR URL",
"syncHint": "When synced, these values are derived from the Service and Paths pages."
},
"install": {
"title": "Installation",
"subtitle": "Click Install to build and deploy ClaudeDo.",
"launch": "Launch ClaudeDo"
},
"settings": {
"removeUserData": "Remove user data (tasks, logs, configs in ~/.todo-app)",
"uninstall": "Uninstall",
"repair": "Repair",
"save": "Save",
"close": "Close"
},
"selfUpdate": {
"heading": "A newer installer is available",
"update": "Update",
"continueAnyway": "Continue anyway"
}
},
"planning": { "planning": {
"conflict": { "conflict": {
"windowTitle": "Merge conflict", "windowTitle": "Merge conflict",