feat(i18n): add language dropdown to settings and persist selection
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
using ClaudeDo.Data.Models;
|
using ClaudeDo.Data.Models;
|
||||||
|
using ClaudeDo.Localization;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
|
|
||||||
public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
|
public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly ILocalizer? _localizer;
|
||||||
|
private readonly Action<string>? _persist;
|
||||||
|
|
||||||
[ObservableProperty] private string _defaultClaudeInstructions = "";
|
[ObservableProperty] private string _defaultClaudeInstructions = "";
|
||||||
[ObservableProperty] private string _defaultModel = ModelRegistry.DefaultAlias;
|
[ObservableProperty] private string _defaultModel = ModelRegistry.DefaultAlias;
|
||||||
[ObservableProperty] private int _defaultMaxTurns = 100;
|
[ObservableProperty] private int _defaultMaxTurns = 100;
|
||||||
@@ -18,6 +22,27 @@ public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
|
|||||||
public IReadOnlyList<string> Models { get; } = ModelRegistry.Aliases;
|
public IReadOnlyList<string> Models { get; } = ModelRegistry.Aliases;
|
||||||
public IReadOnlyList<string> PermissionModes { get; } = PermissionModeRegistry.Modes;
|
public IReadOnlyList<string> PermissionModes { get; } = PermissionModeRegistry.Modes;
|
||||||
|
|
||||||
|
public GeneralSettingsTabViewModel() { }
|
||||||
|
|
||||||
|
public GeneralSettingsTabViewModel(ILocalizer localizer, Action<string> persist)
|
||||||
|
{
|
||||||
|
_localizer = localizer;
|
||||||
|
_persist = persist;
|
||||||
|
Languages = localizer.AvailableLanguages;
|
||||||
|
_selectedLanguage = Languages.FirstOrDefault(l => l.Code == localizer.CurrentCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<LanguageOption> Languages { get; } = Array.Empty<LanguageOption>();
|
||||||
|
|
||||||
|
[ObservableProperty] private LanguageOption? _selectedLanguage;
|
||||||
|
|
||||||
|
partial void OnSelectedLanguageChanged(LanguageOption? value)
|
||||||
|
{
|
||||||
|
if (value is null || _localizer is null) return;
|
||||||
|
_localizer.SetLanguage(value.Value.Code);
|
||||||
|
_persist?.Invoke(value.Value.Code);
|
||||||
|
}
|
||||||
|
|
||||||
public string? Validate()
|
public string? Validate()
|
||||||
{
|
{
|
||||||
if (DefaultMaxTurns < 1 || DefaultMaxTurns > 200)
|
if (DefaultMaxTurns < 1 || DefaultMaxTurns > 200)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using ClaudeDo.Data;
|
using ClaudeDo.Data;
|
||||||
|
using ClaudeDo.Localization;
|
||||||
using ClaudeDo.Ui.Services;
|
using ClaudeDo.Ui.Services;
|
||||||
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
@@ -22,10 +23,15 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
|||||||
|
|
||||||
public Action? CloseAction { get; set; }
|
public Action? CloseAction { get; set; }
|
||||||
|
|
||||||
public SettingsModalViewModel(WorkerClient worker, PrimeClaudeTabViewModel prime)
|
public SettingsModalViewModel(WorkerClient worker, PrimeClaudeTabViewModel prime,
|
||||||
|
ILocalizer localizer, AppSettings appSettings)
|
||||||
{
|
{
|
||||||
_worker = worker;
|
_worker = worker;
|
||||||
General = new GeneralSettingsTabViewModel();
|
General = new GeneralSettingsTabViewModel(localizer, code =>
|
||||||
|
{
|
||||||
|
appSettings.Language = code;
|
||||||
|
appSettings.Save();
|
||||||
|
});
|
||||||
Worktrees = new WorktreesSettingsTabViewModel(worker);
|
Worktrees = new WorktreesSettingsTabViewModel(worker);
|
||||||
Files = new FilesSettingsTabViewModel(worker);
|
Files = new FilesSettingsTabViewModel(worker);
|
||||||
Prime = prime;
|
Prime = prime;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||||
xmlns:settings="using:ClaudeDo.Ui.ViewModels.Modals.Settings"
|
xmlns:settings="using:ClaudeDo.Ui.ViewModels.Modals.Settings"
|
||||||
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||||
|
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||||
|
xmlns:locm="using:ClaudeDo.Localization"
|
||||||
x:Class="ClaudeDo.Ui.Views.Modals.SettingsModalView"
|
x:Class="ClaudeDo.Ui.Views.Modals.SettingsModalView"
|
||||||
x:DataType="vm:SettingsModalViewModel"
|
x:DataType="vm:SettingsModalViewModel"
|
||||||
Title="Settings"
|
Title="Settings"
|
||||||
@@ -45,6 +47,18 @@
|
|||||||
<TabItem Header="General">
|
<TabItem Header="General">
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="12" Margin="0,8,0,0">
|
<StackPanel Spacing="12" Margin="0,8,0,0">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Classes="field-label" Text="{loc:Tr settings.language}"/>
|
||||||
|
<ComboBox ItemsSource="{Binding General.Languages}"
|
||||||
|
SelectedItem="{Binding General.SelectedLanguage, Mode=TwoWay}"
|
||||||
|
HorizontalAlignment="Left" Width="220">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="locm:LanguageOption">
|
||||||
|
<TextBlock Text="{Binding Name}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Classes="field-label" Text="Default instructions"/>
|
<TextBlock Classes="field-label" Text="Default instructions"/>
|
||||||
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Height="110"
|
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Height="110"
|
||||||
|
|||||||
35
tests/ClaudeDo.Ui.Tests/LanguageSettingTests.cs
Normal file
35
tests/ClaudeDo.Ui.Tests/LanguageSettingTests.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using ClaudeDo.Localization;
|
||||||
|
using ClaudeDo.Ui;
|
||||||
|
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Tests;
|
||||||
|
|
||||||
|
public class LanguageSettingTests
|
||||||
|
{
|
||||||
|
private static Localizer Make()
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(Path.GetTempPath(), "loc_" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
File.WriteAllText(Path.Combine(dir, "en.json"),
|
||||||
|
"""{ "metadata": { "code": "en", "name": "English" }, "settings": { "save": "Save" } }""");
|
||||||
|
File.WriteAllText(Path.Combine(dir, "de.json"),
|
||||||
|
"""{ "metadata": { "code": "de", "name": "Deutsch" }, "settings": { "save": "Speichern" } }""");
|
||||||
|
return new Localizer(LocaleStore.Load(dir), "en");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selecting_language_switches_localizer_and_persists()
|
||||||
|
{
|
||||||
|
var loc = Make();
|
||||||
|
var settings = new AppSettings();
|
||||||
|
var saved = false;
|
||||||
|
var vm = new GeneralSettingsTabViewModel(loc, code => { settings.Language = code; saved = true; });
|
||||||
|
|
||||||
|
var de = vm.Languages.First(l => l.Code == "de");
|
||||||
|
vm.SelectedLanguage = de;
|
||||||
|
|
||||||
|
Assert.Equal("de", loc.CurrentCode);
|
||||||
|
Assert.True(saved);
|
||||||
|
Assert.Equal("de", settings.Language);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user