feat(i18n): localize ViewModel-built strings via ambient Loc accessor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,7 @@ sealed class Program
|
||||
fallback: "en");
|
||||
var localizer = new Localizer(localeStore, initialLang);
|
||||
TrExtension.Localizer = localizer;
|
||||
ClaudeDo.Ui.Localization.Loc.Current = localizer;
|
||||
sc.AddSingleton<ILocalizer>(localizer);
|
||||
sc.AddDbContextFactory<ClaudeDoDbContext>(opt =>
|
||||
opt.UseSqlite($"Data Source={dbPath}"));
|
||||
|
||||
@@ -302,5 +302,26 @@
|
||||
"updateNow": "Update now",
|
||||
"dismiss": "Dismiss"
|
||||
}
|
||||
},
|
||||
"vm": {
|
||||
"connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" },
|
||||
"shell": { "restartingWorker": "Restarting worker…" },
|
||||
"agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
|
||||
"taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
|
||||
"planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },
|
||||
"taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" },
|
||||
"tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}" },
|
||||
"diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show." },
|
||||
"planningDiff": { "hubError": "Could not build combined preview (hub error).", "conflict": "Cannot build combined preview: subtask {0} conflicts with an earlier subtask ({1} files)." },
|
||||
"merge": { "commitMessage": "Merge task: {0}", "workerOfflineBranches": "Worker offline — cannot list branches.", "loadBranchesFailed": "Failed to load branches: {0}", "merged": "Merged.", "conflict": "Merge conflict — target branch restored. Resolve manually or via Continue, then retry.", "blocked": "Blocked: {0}", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" },
|
||||
"conflictResolution": { "vsCodeError": "Could not launch VS Code: {0}. Paths are listed above — copy them manually.", "subtaskPrefix": "Conflicts in subtask: {0}", "targetPrefix": "Merging into: {0}" },
|
||||
"settingsModal": { "workerOffline": "Worker offline — settings read-only.", "saveFailed": "Save failed: {0}" },
|
||||
"weeklyReport": { "invalidRange": "Invalid date range.", "generating": "Generating report…", "error": "Error: {0}" },
|
||||
"filesTab": { "workerOffline": "Worker offline.", "noneBundled": "No default agents bundled.", "allPresent": "All default agents already present.", "restored": "Restored {0} default agent(s).", "restoreFailed": "Restore failed: {0}", "openFailed": "Open failed: {0}" },
|
||||
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "Removed {0} worktree(s).", "blocked": "Cannot force-remove: {0} task(s) still running. Cancel them first.", "removedFrom": "Removed {0} worktree(s) from {1} task(s)." },
|
||||
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "list", "cleanupFailed": "Cleanup failed.", "removed": "Removed {0} worktree(s).", "discardFailed": "Failed to discard worktree.", "keepFailed": "Failed to keep worktree.", "cannotForceRunning": "Cannot force-remove a running task.", "forceRemoveFailed": "Force remove failed." },
|
||||
"listSettings": { "untitled": "Untitled" },
|
||||
"details": { "effectiveIfInherited": "Effective if inherited: {0}" },
|
||||
"lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list" }
|
||||
}
|
||||
}
|
||||
|
||||
43
src/ClaudeDo.Ui/Localization/Loc.cs
Normal file
43
src/ClaudeDo.Ui/Localization/Loc.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ClaudeDo.Localization;
|
||||
|
||||
namespace ClaudeDo.Ui.Localization;
|
||||
|
||||
/// Ambient access to the active localizer for code-built (ViewModel) strings.
|
||||
/// Set once at startup. Defaults to a key-echo localizer so unit tests that
|
||||
/// construct ViewModels without startup wiring do not crash.
|
||||
public static class Loc
|
||||
{
|
||||
private static ILocalizer _current = new KeyEchoLocalizer();
|
||||
|
||||
public static ILocalizer Current
|
||||
{
|
||||
get => _current;
|
||||
set
|
||||
{
|
||||
if (_current is not null) _current.LanguageChanged -= OnInnerChanged;
|
||||
_current = value;
|
||||
_current.LanguageChanged += OnInnerChanged;
|
||||
OnInnerChanged(value, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public static event EventHandler? LanguageChanged;
|
||||
|
||||
private static void OnInnerChanged(object? sender, EventArgs e) =>
|
||||
LanguageChanged?.Invoke(sender, e);
|
||||
|
||||
public static string T(string key) => Current[key];
|
||||
public static string T(string key, params object[] args) => Current.Get(key, args);
|
||||
|
||||
private sealed class KeyEchoLocalizer : ILocalizer
|
||||
{
|
||||
public string this[string key] => key;
|
||||
public string Get(string key, params object[] args) => key;
|
||||
public string CurrentCode => "en";
|
||||
public IReadOnlyList<LanguageOption> AvailableLanguages => Array.Empty<LanguageOption>();
|
||||
public void SetLanguage(string code) { }
|
||||
public event EventHandler? LanguageChanged { add { } remove { } }
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Helpers;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.Services.Interfaces;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
@@ -99,13 +100,14 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
[NotifyCanExecuteChangedFor(nameof(DequeueCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ResetAndRetryCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ContinueCommand))]
|
||||
private string _agentStatusLabel = "Idle";
|
||||
public bool IsIdle => AgentStatusLabel == "Idle";
|
||||
public bool IsQueued => AgentStatusLabel == "Queued";
|
||||
public bool IsRunning => AgentStatusLabel == "Running";
|
||||
public bool IsDone => AgentStatusLabel == "Done";
|
||||
public bool IsFailed => AgentStatusLabel == "Failed";
|
||||
public bool IsCancelled => AgentStatusLabel == "Cancelled";
|
||||
private string _agentState = "idle";
|
||||
public string AgentStatusLabel => Loc.T($"vm.agentStatus.{AgentState}");
|
||||
public bool IsIdle => AgentState == "idle";
|
||||
public bool IsQueued => AgentState == "queued";
|
||||
public bool IsRunning => AgentState == "running";
|
||||
public bool IsDone => AgentState == "done";
|
||||
public bool IsFailed => AgentState == "failed";
|
||||
public bool IsCancelled => AgentState == "cancelled";
|
||||
|
||||
// Recovery actions: Continue (resume session) for Failed/Cancelled.
|
||||
public bool ShowContinue => IsFailed || IsCancelled;
|
||||
@@ -116,8 +118,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
[NotifyCanExecuteChangedFor(nameof(ContinueCommand))]
|
||||
private string? _latestRunSessionId;
|
||||
|
||||
partial void OnAgentStatusLabelChanged(string value)
|
||||
partial void OnAgentStateChanged(string value)
|
||||
{
|
||||
OnPropertyChanged(nameof(AgentStatusLabel));
|
||||
OnPropertyChanged(nameof(IsIdle));
|
||||
OnPropertyChanged(nameof(IsQueued));
|
||||
OnPropertyChanged(nameof(IsRunning));
|
||||
@@ -127,6 +130,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(ShowContinue));
|
||||
OnPropertyChanged(nameof(ShowResetAndRetry));
|
||||
OnPropertyChanged(nameof(IsAgentSectionEnabled));
|
||||
OnPropertyChanged(nameof(EffectiveModelLabel));
|
||||
OnPropertyChanged(nameof(EffectiveAgentLabel));
|
||||
}
|
||||
[ObservableProperty] private string? _model;
|
||||
|
||||
@@ -139,6 +144,12 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
[ObservableProperty] private string _effectiveSystemPromptHint = "";
|
||||
[ObservableProperty] private string _effectiveAgentHint = "";
|
||||
|
||||
public string EffectiveModelLabel => Loc.T("vm.details.effectiveIfInherited", EffectiveModelHint);
|
||||
public string EffectiveAgentLabel => Loc.T("vm.details.effectiveIfInherited", EffectiveAgentHint);
|
||||
|
||||
partial void OnEffectiveModelHintChanged(string value) => OnPropertyChanged(nameof(EffectiveModelLabel));
|
||||
partial void OnEffectiveAgentHintChanged(string value) => OnPropertyChanged(nameof(EffectiveAgentLabel));
|
||||
|
||||
public System.Collections.ObjectModel.ObservableCollection<string> TaskModelOptions { get; } = new(
|
||||
new[] { ModelRegistry.TaskInheritSentinel }.Concat(ModelRegistry.Aliases));
|
||||
|
||||
@@ -223,6 +234,26 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
// Set by the view so DeleteTaskCommand can show an error message
|
||||
public Func<string, System.Threading.Tasks.Task>? ShowErrorAsync { get; set; }
|
||||
|
||||
private static string StatusToStateKey(ClaudeDo.Data.Models.TaskStatus status) => status switch
|
||||
{
|
||||
ClaudeDo.Data.Models.TaskStatus.Queued => "queued",
|
||||
ClaudeDo.Data.Models.TaskStatus.Running => "running",
|
||||
ClaudeDo.Data.Models.TaskStatus.WaitingForReview => "running",
|
||||
ClaudeDo.Data.Models.TaskStatus.Done => "done",
|
||||
ClaudeDo.Data.Models.TaskStatus.Failed => "failed",
|
||||
ClaudeDo.Data.Models.TaskStatus.Cancelled => "cancelled",
|
||||
_ => "idle",
|
||||
};
|
||||
|
||||
private static string FinishedStatusToStateKey(string status) => status switch
|
||||
{
|
||||
"done" => "done",
|
||||
"failed" => "failed",
|
||||
"cancelled" => "cancelled",
|
||||
"waiting_for_review" => "running",
|
||||
_ => status.ToLowerInvariant(),
|
||||
};
|
||||
|
||||
private async System.Threading.Tasks.Task RefreshStatusAsync(string taskId)
|
||||
{
|
||||
try
|
||||
@@ -233,7 +264,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
.FirstOrDefaultAsync(t => t.Id == taskId);
|
||||
if (entity is null || Task?.Id != taskId) return;
|
||||
|
||||
AgentStatusLabel = entity.Status.ToString();
|
||||
AgentState = StatusToStateKey(entity.Status);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -245,6 +276,12 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
_services = services;
|
||||
_notesApi = notesApi;
|
||||
Notes = new NotesEditorViewModel(_notesApi);
|
||||
Loc.LanguageChanged += (_, _) =>
|
||||
{
|
||||
OnPropertyChanged(nameof(AgentStatusLabel));
|
||||
OnPropertyChanged(nameof(EffectiveModelLabel));
|
||||
OnPropertyChanged(nameof(EffectiveAgentLabel));
|
||||
};
|
||||
|
||||
// Subscribe once; filter by current task id inside the handler
|
||||
_worker.TaskMessageEvent += OnTaskMessage;
|
||||
@@ -264,7 +301,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
// If the task row's live status changes (e.g. TaskStarted/Finished), mirror it.
|
||||
_worker.TaskStartedEvent += (slot, taskId, startedAt) =>
|
||||
{
|
||||
if (Task?.Id == taskId) AgentStatusLabel = "Running";
|
||||
if (Task?.Id == taskId) AgentState = "running";
|
||||
};
|
||||
_worker.TaskFinishedEvent += (slot, taskId, status, finishedAt) =>
|
||||
{
|
||||
@@ -275,7 +312,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
Kind = LogKind.Done,
|
||||
Text = $"── {status.ToUpperInvariant()} · {finishedAt.ToLocalTime():HH:mm:ss} ──",
|
||||
});
|
||||
AgentStatusLabel = status;
|
||||
AgentState = FinishedStatusToStateKey(status);
|
||||
// Re-query to pick up worktree created during the run.
|
||||
_ = RefreshWorktreeAsync(taskId);
|
||||
};
|
||||
@@ -473,7 +510,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
WorktreePath = null;
|
||||
WorktreeStateLabel = null;
|
||||
BranchLine = null;
|
||||
AgentStatusLabel = "Idle";
|
||||
AgentState = "idle";
|
||||
LatestRunSessionId = null;
|
||||
_suppressAgentSave = true;
|
||||
try
|
||||
@@ -519,7 +556,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
WorktreeBaseCommit = entity.Worktree?.BaseCommit;
|
||||
WorktreeStateLabel = entity.Worktree?.State.ToString();
|
||||
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} \u2190 main" : null;
|
||||
AgentStatusLabel = entity.Status.ToString();
|
||||
AgentState = StatusToStateKey(entity.Status);
|
||||
await LoadAgentSettingsAsync(entity, ct);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -730,7 +767,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
WorktreeBaseCommit = entity.Worktree?.BaseCommit;
|
||||
WorktreeStateLabel = entity.Worktree?.State.ToString();
|
||||
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} ← main" : null;
|
||||
AgentStatusLabel = entity.Status.ToString();
|
||||
AgentState = StatusToStateKey(entity.Status);
|
||||
if (Task is { } row && entity.Worktree?.DiffStat is { } stat)
|
||||
row.DiffStat = stat;
|
||||
}
|
||||
@@ -808,7 +845,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
? ClaudeDo.Data.Models.TaskStatus.Done
|
||||
: ClaudeDo.Data.Models.TaskStatus.Idle;
|
||||
Task.Status = entity.Status;
|
||||
AgentStatusLabel = entity.Status.ToString();
|
||||
AgentState = StatusToStateKey(entity.Status);
|
||||
await repo.UpdateAsync(entity);
|
||||
}
|
||||
|
||||
@@ -922,7 +959,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Queued);
|
||||
AgentStatusLabel = "Queued";
|
||||
AgentState = "queued";
|
||||
}
|
||||
catch { /* offline */ }
|
||||
}
|
||||
@@ -938,7 +975,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Idle);
|
||||
AgentStatusLabel = "Idle";
|
||||
AgentState = "idle";
|
||||
}
|
||||
catch { /* offline */ }
|
||||
}
|
||||
@@ -973,7 +1010,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Queued);
|
||||
AgentStatusLabel = "Queued";
|
||||
AgentState = "queued";
|
||||
}
|
||||
catch { /* offline */ }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using ClaudeDo.Data.Filtering;
|
||||
using ClaudeDo.Data.Models;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -137,6 +138,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
||||
|
||||
public string UserName { get; } = Environment.UserName;
|
||||
public string MachineName { get; } = Environment.MachineName;
|
||||
public string MachineNameLocal => Loc.T("vm.lists.localSuffix", MachineName);
|
||||
public string UserInitials { get; }
|
||||
|
||||
public ListsIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IServiceProvider? services = null, WorkerClient? worker = null)
|
||||
@@ -170,12 +172,12 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
||||
|
||||
var smart = new[]
|
||||
{
|
||||
new ListNavItemViewModel { Id = "smart:my-day", Name = "My Day", Kind = ListKind.Smart, IconKey = "Sun" },
|
||||
new ListNavItemViewModel { Id = "smart:important", Name = "Important", Kind = ListKind.Smart, IconKey = "Star" },
|
||||
new ListNavItemViewModel { Id = "smart:planned", Name = "Planned", Kind = ListKind.Smart, IconKey = "Calendar" },
|
||||
new ListNavItemViewModel { Id = "virtual:queued", Name = "Queue", Kind = ListKind.Virtual, IconKey = "Inbox" },
|
||||
new ListNavItemViewModel { Id = "virtual:running", Name = "Running", Kind = ListKind.Virtual, IconKey = "Activity" },
|
||||
new ListNavItemViewModel { Id = "virtual:review", Name = "Review", Kind = ListKind.Virtual, IconKey = "Eye" },
|
||||
new ListNavItemViewModel { Id = "smart:my-day", Name = Loc.T("vm.lists.smartMyDay"), Kind = ListKind.Smart, IconKey = "Sun" },
|
||||
new ListNavItemViewModel { Id = "smart:important", Name = Loc.T("vm.lists.smartImportant"), Kind = ListKind.Smart, IconKey = "Star" },
|
||||
new ListNavItemViewModel { Id = "smart:planned", Name = Loc.T("vm.lists.smartPlanned"), Kind = ListKind.Smart, IconKey = "Calendar" },
|
||||
new ListNavItemViewModel { Id = "virtual:queued", Name = Loc.T("vm.lists.virtualQueue"), Kind = ListKind.Virtual, IconKey = "Inbox" },
|
||||
new ListNavItemViewModel { Id = "virtual:running", Name = Loc.T("vm.lists.virtualRunning"), Kind = ListKind.Virtual, IconKey = "Activity" },
|
||||
new ListNavItemViewModel { Id = "virtual:review", Name = Loc.T("vm.lists.virtualReview"), Kind = ListKind.Virtual, IconKey = "Eye" },
|
||||
};
|
||||
foreach (var s in smart) { Items.Add(s); SmartLists.Add(s); }
|
||||
|
||||
@@ -242,7 +244,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
||||
var entity = new ListEntity
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Name = "New list",
|
||||
Name = Loc.T("vm.lists.newList"),
|
||||
DefaultCommitType = CommitTypeRegistry.DefaultType,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Islands;
|
||||
@@ -31,7 +32,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
[ObservableProperty] private bool _parentFinalized;
|
||||
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public string CreatedAtFormatted => CreatedAt == default ? "—" : $"Created {CreatedAt:MMM d}";
|
||||
public string CreatedAtFormatted => CreatedAt == default ? "—" : Loc.T("vm.taskRow.createdPrefix", CreatedAt.ToString("MMM d"));
|
||||
|
||||
public int StepsCount { get; init; }
|
||||
public int StepsCompleted { get; init; }
|
||||
@@ -50,8 +51,8 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
|
||||
public string? PlanningBadge => PlanningPhase switch
|
||||
{
|
||||
PlanningPhase.Active => "PLANNING",
|
||||
PlanningPhase.Finalized => "PLANNED",
|
||||
PlanningPhase.Active => Loc.T("vm.planningBadge.active"),
|
||||
PlanningPhase.Finalized => Loc.T("vm.planningBadge.finalized"),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -77,9 +78,19 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
|
||||
public string DiffAdditionsText => $"+{DiffAdditions}";
|
||||
public string DiffDeletionsText => $"−{DiffDeletions}";
|
||||
public string StepsText => $"{StepsCompleted}/{StepsCount} steps";
|
||||
public string StepsText => Loc.T("vm.taskRow.stepsText", StepsCompleted, StepsCount);
|
||||
|
||||
public string StatusLabel => Status == TaskStatus.WaitingForReview ? "Waiting for Review" : Status.ToString();
|
||||
public string StatusLabel => Status switch
|
||||
{
|
||||
TaskStatus.Idle => Loc.T("vm.taskStatus.idle"),
|
||||
TaskStatus.Queued => Loc.T("vm.taskStatus.queued"),
|
||||
TaskStatus.Running => Loc.T("vm.taskStatus.running"),
|
||||
TaskStatus.WaitingForReview => Loc.T("vm.taskStatus.waitingForReview"),
|
||||
TaskStatus.Done => Loc.T("vm.taskStatus.done"),
|
||||
TaskStatus.Failed => Loc.T("vm.taskStatus.failed"),
|
||||
TaskStatus.Cancelled => Loc.T("vm.taskStatus.cancelled"),
|
||||
_ => Status.ToString(),
|
||||
};
|
||||
|
||||
public string StatusChipClass => (Status, IsBlocked: !string.IsNullOrEmpty(BlockedByTaskId)) switch
|
||||
{
|
||||
@@ -164,6 +175,14 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffAdditionsText)); }
|
||||
partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); }
|
||||
|
||||
public void RefreshLocalized()
|
||||
{
|
||||
OnPropertyChanged(nameof(StatusLabel));
|
||||
OnPropertyChanged(nameof(PlanningBadge));
|
||||
OnPropertyChanged(nameof(CreatedAtFormatted));
|
||||
OnPropertyChanged(nameof(StepsText));
|
||||
}
|
||||
|
||||
public static TaskRowViewModel FromEntity(TaskEntity t)
|
||||
{
|
||||
var row = new TaskRowViewModel { Id = t.Id, CreatedAt = t.CreatedAt };
|
||||
|
||||
@@ -6,6 +6,7 @@ using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Filtering;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -52,7 +53,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
[ObservableProperty] private bool _hasOpen;
|
||||
[ObservableProperty] private bool _hasCompleted;
|
||||
[ObservableProperty] private bool _showOpenLabel;
|
||||
[ObservableProperty] private string _completedHeader = "COMPLETED";
|
||||
[ObservableProperty] private string _completedHeader = "";
|
||||
[ObservableProperty] private bool _showNotesRow;
|
||||
|
||||
public Func<UnfinishedPlanningModalViewModel, Task>? ShowUnfinishedPlanningModal { get; set; }
|
||||
@@ -61,6 +62,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_worker = worker;
|
||||
CompletedHeader = Loc.T("vm.tasksIsland.completedHeader");
|
||||
if (_worker is not null)
|
||||
{
|
||||
_worker.TaskUpdatedEvent += OnWorkerTaskUpdated;
|
||||
@@ -68,6 +70,14 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
_worker.ListUpdatedEvent += OnWorkerListUpdated;
|
||||
_worker.ConnectionRestoredEvent += () => LoadForList(_currentList);
|
||||
}
|
||||
Loc.LanguageChanged += (_, _) => RefreshLocalizedText();
|
||||
}
|
||||
|
||||
private void RefreshLocalizedText()
|
||||
{
|
||||
CompletedHeader = Loc.T("vm.tasksIsland.completedHeader");
|
||||
foreach (var row in Items) row.RefreshLocalized();
|
||||
foreach (var row in CompletedItems) row.RefreshLocalized();
|
||||
}
|
||||
|
||||
private async void OnWorkerListUpdated(string listId)
|
||||
@@ -340,7 +350,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
HasOpen = OpenItems.Count > 0;
|
||||
HasCompleted = CompletedItems.Count > 0;
|
||||
ShowOpenLabel = HasOpen && HasOverdue;
|
||||
CompletedHeader = $"COMPLETED · {CompletedItems.Count}";
|
||||
CompletedHeader = Loc.T("vm.tasksIsland.completedHeaderCount", CompletedItems.Count);
|
||||
}
|
||||
|
||||
private void UpdateSubtitle()
|
||||
|
||||
@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
@@ -23,9 +24,9 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
||||
public UpdateCheckService UpdateCheck => _updateCheck;
|
||||
|
||||
public string ConnectionText =>
|
||||
Worker?.IsConnected == true ? "Online"
|
||||
: Worker?.IsReconnecting == true ? "Connecting…"
|
||||
: "Offline";
|
||||
Worker?.IsConnected == true ? Loc.T("vm.connection.online")
|
||||
: Worker?.IsReconnecting == true ? Loc.T("vm.connection.connecting")
|
||||
: Loc.T("vm.connection.offline");
|
||||
|
||||
public bool IsOffline => Worker?.IsConnected != true && Worker?.IsReconnecting != true;
|
||||
|
||||
@@ -358,7 +359,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
||||
[RelayCommand]
|
||||
private async Task RestartWorkerAsync()
|
||||
{
|
||||
RestartWorkerStatus = "Restarting worker…";
|
||||
RestartWorkerStatus = Loc.T("vm.shell.restartingWorker");
|
||||
try
|
||||
{
|
||||
await Task.Run(RestartWorkerService);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Data.Git;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
@@ -91,13 +92,13 @@ public sealed partial class DiffModalViewModel : ViewModelBase
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Failed to load diff: {ex.Message}";
|
||||
StatusMessage = Loc.T("vm.diff.loadFailed", ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
StatusMessage = "No changes to show.";
|
||||
StatusMessage = Loc.T("vm.diff.noChanges");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
SelectedFile = Files.Count > 0 ? Files[0] : null;
|
||||
if (Files.Count == 0) StatusMessage = "No changes to show.";
|
||||
if (Files.Count == 0) StatusMessage = Loc.T("vm.diff.noChanges");
|
||||
}
|
||||
|
||||
private static void ParseHunkHeader(string header, out int oldStart, out int newStart)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -80,7 +81,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
|
||||
await _worker.UpdateListAsync(new UpdateListDto(
|
||||
ListId,
|
||||
string.IsNullOrWhiteSpace(Name) ? "Untitled" : Name,
|
||||
string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name,
|
||||
string.IsNullOrWhiteSpace(WorkingDir) ? null : WorkingDir,
|
||||
DefaultCommitType));
|
||||
|
||||
@@ -93,7 +94,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
[RelayCommand]
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
var displayName = string.IsNullOrWhiteSpace(Name) ? "Untitled" : Name;
|
||||
var displayName = string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name;
|
||||
if (ConfirmAsync is not null)
|
||||
{
|
||||
var ok = await ConfirmAsync($"Delete list \"{displayName}\" and all its tasks? This cannot be undone.");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -36,7 +37,7 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
{
|
||||
TaskId = taskId;
|
||||
TaskTitle = taskTitle;
|
||||
CommitMessage = $"Merge task: {taskTitle}";
|
||||
CommitMessage = Loc.T("vm.merge.commitMessage", taskTitle);
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
@@ -45,7 +46,7 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
Branches.Clear();
|
||||
if (targets is null)
|
||||
{
|
||||
ErrorMessage = "Worker offline — cannot list branches.";
|
||||
ErrorMessage = Loc.T("vm.merge.workerOfflineBranches");
|
||||
return;
|
||||
}
|
||||
foreach (var b in targets.LocalBranches) Branches.Add(b);
|
||||
@@ -55,7 +56,7 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = $"Failed to load branches: {ex.Message}";
|
||||
ErrorMessage = Loc.T("vm.merge.loadBranchesFailed", ex.Message);
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
@@ -81,7 +82,7 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
case "merged":
|
||||
SuccessMessage = result.ErrorMessage is not null
|
||||
? $"Merged with warning: {result.ErrorMessage}"
|
||||
: "Merged.";
|
||||
: Loc.T("vm.merge.merged");
|
||||
// Auto-close after a short delay.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
@@ -92,19 +93,19 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
case "conflict":
|
||||
HasConflict = true;
|
||||
ConflictFiles = result.ConflictFiles;
|
||||
ErrorMessage = "Merge conflict — target branch restored. Resolve manually or via Continue, then retry.";
|
||||
ErrorMessage = Loc.T("vm.merge.conflict");
|
||||
break;
|
||||
case "blocked":
|
||||
ErrorMessage = $"Blocked: {result.ErrorMessage}";
|
||||
ErrorMessage = Loc.T("vm.merge.blocked", result.ErrorMessage ?? "");
|
||||
break;
|
||||
default:
|
||||
ErrorMessage = $"Unknown status: {result.Status}";
|
||||
ErrorMessage = Loc.T("vm.merge.unknownStatus", result.Status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = $"Merge failed: {ex.Message}";
|
||||
ErrorMessage = Loc.T("vm.merge.mergeFailed", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -26,13 +27,13 @@ public sealed partial class FilesSettingsTabViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
var r = await _worker.RestoreDefaultAgentsAsync();
|
||||
if (r is null) StatusMessage = "Worker offline.";
|
||||
else if (r.Copied == 0 && r.Skipped == 0) StatusMessage = "No default agents bundled.";
|
||||
else if (r.Copied == 0) StatusMessage = "All default agents already present.";
|
||||
else StatusMessage = $"Restored {r.Copied} default agent(s).";
|
||||
if (r is null) StatusMessage = Loc.T("vm.filesTab.workerOffline");
|
||||
else if (r.Copied == 0 && r.Skipped == 0) StatusMessage = Loc.T("vm.filesTab.noneBundled");
|
||||
else if (r.Copied == 0) StatusMessage = Loc.T("vm.filesTab.allPresent");
|
||||
else StatusMessage = Loc.T("vm.filesTab.restored", r.Copied);
|
||||
await _worker.RefreshAgentsAsync();
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Restore failed: {ex.Message}"; }
|
||||
catch (Exception ex) { StatusMessage = Loc.T("vm.filesTab.restoreFailed", ex.Message); }
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
|
||||
@@ -46,6 +47,6 @@ public sealed partial class FilesSettingsTabViewModel : ViewModelBase
|
||||
var path = PromptFiles.PathFor(kind);
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Open failed: {ex.Message}"; }
|
||||
catch (Exception ex) { StatusMessage = Loc.T("vm.filesTab.openFailed", ex.Message); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -43,7 +44,7 @@ public sealed partial class WorktreesSettingsTabViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
var r = await _worker.CleanupFinishedWorktreesAsync();
|
||||
StatusMessage = r is null ? "Worker offline." : $"Removed {r.Removed} worktree(s).";
|
||||
StatusMessage = r is null ? Loc.T("vm.worktreesTab.workerOffline") : Loc.T("vm.worktreesTab.removed", r.Removed);
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
@@ -58,9 +59,9 @@ public sealed partial class WorktreesSettingsTabViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
var r = await _worker.ResetAllWorktreesAsync();
|
||||
if (r is null) StatusMessage = "Worker offline.";
|
||||
else if (r.Blocked) StatusMessage = $"Cannot force-remove: {r.RunningTasks} task(s) still running. Cancel them first.";
|
||||
else StatusMessage = $"Removed {r.Removed} worktree(s) from {r.TasksAffected} task(s).";
|
||||
if (r is null) StatusMessage = Loc.T("vm.worktreesTab.workerOffline");
|
||||
else if (r.Blocked) StatusMessage = Loc.T("vm.worktreesTab.blocked", r.RunningTasks);
|
||||
else StatusMessage = Loc.T("vm.worktreesTab.removedFrom", r.Removed, r.TasksAffected);
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Localization;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@@ -60,7 +61,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
||||
System.Text.Json.JsonSerializer.Deserialize<List<string>>(dto.ReportExcludedPaths) ?? new());
|
||||
General.StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday;
|
||||
}
|
||||
else StatusMessage = "Worker offline — settings read-only.";
|
||||
else StatusMessage = Loc.T("vm.settingsModal.workerOffline");
|
||||
|
||||
await Prime.LoadAsync();
|
||||
}
|
||||
@@ -95,7 +96,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
||||
await Prime.SaveAsync();
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Save failed: {ex.Message}"; }
|
||||
catch (Exception ex) { StatusMessage = Loc.T("vm.settingsModal.saveFailed", ex.Message); }
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -72,16 +73,16 @@ public sealed partial class WeeklyReportModalViewModel : ViewModelBase
|
||||
[RelayCommand(CanExecute = nameof(CanGenerate))]
|
||||
private async Task Generate()
|
||||
{
|
||||
if (!RangeValid) { StatusMessage = "Invalid date range."; return; }
|
||||
if (!RangeValid) { StatusMessage = Loc.T("vm.weeklyReport.invalidRange"); return; }
|
||||
IsBusy = true;
|
||||
StatusMessage = "Generating report…";
|
||||
StatusMessage = Loc.T("vm.weeklyReport.generating");
|
||||
try
|
||||
{
|
||||
ReportMarkdown = await _worker.GenerateWeekReportAsync(
|
||||
DateOnly.FromDateTime(StartDate!.Value), DateOnly.FromDateTime(EndDate!.Value));
|
||||
StatusMessage = "";
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Error: {ex.Message}"; }
|
||||
catch (Exception ex) { StatusMessage = Loc.T("vm.weeklyReport.error", ex.Message); }
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input.Platform;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -86,7 +87,9 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
{
|
||||
ListIdFilter = listId;
|
||||
IsGlobal = listId is null;
|
||||
Title = listId is null ? "Worktrees" : $"Worktrees — {listName ?? "list"}";
|
||||
Title = listId is null
|
||||
? Loc.T("vm.worktreesOverview.titleAll")
|
||||
: Loc.T("vm.worktreesOverview.titleList", listName ?? Loc.T("vm.worktreesOverview.listFallback"));
|
||||
}
|
||||
|
||||
public async Task LoadAsync(CancellationToken ct = default)
|
||||
@@ -138,7 +141,7 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
var result = await _worker.CleanupFinishedWorktreesAsync(ListIdFilter);
|
||||
StatusMessage = result is null ? "Cleanup failed." : $"Removed {result.Removed} worktree(s).";
|
||||
StatusMessage = result is null ? Loc.T("vm.worktreesOverview.cleanupFailed") : Loc.T("vm.worktreesOverview.removed", result.Removed);
|
||||
await LoadAsync();
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
@@ -190,7 +193,7 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
if (row is null || row.State != WorktreeState.Active) return;
|
||||
var (ok, err) = await _worker.SetWorktreeStateAsync(row.TaskId, WorktreeState.Discarded);
|
||||
if (ok) row.State = WorktreeState.Discarded;
|
||||
else StatusMessage = err ?? "Failed to discard worktree.";
|
||||
else StatusMessage = err ?? Loc.T("vm.worktreesOverview.discardFailed");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -199,20 +202,20 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
if (row is null || row.State != WorktreeState.Active) return;
|
||||
var (ok, err) = await _worker.SetWorktreeStateAsync(row.TaskId, WorktreeState.Kept);
|
||||
if (ok) row.State = WorktreeState.Kept;
|
||||
else StatusMessage = err ?? "Failed to keep worktree.";
|
||||
else StatusMessage = err ?? Loc.T("vm.worktreesOverview.keepFailed");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ForceRemove(WorktreeOverviewRowViewModel? row)
|
||||
{
|
||||
if (row is null) return;
|
||||
if (row.IsRunning) { StatusMessage = "Cannot force-remove a running task."; return; }
|
||||
if (row.IsRunning) { StatusMessage = Loc.T("vm.worktreesOverview.cannotForceRunning"); return; }
|
||||
if (ConfirmAction is not null && !await ConfirmAction($"Force remove worktree for '{row.TaskTitle}'? This deletes the directory and branch.")) return;
|
||||
|
||||
var result = await _worker.ForceRemoveWorktreeAsync(row.TaskId);
|
||||
if (result is null || !result.Removed)
|
||||
{
|
||||
StatusMessage = result?.Reason ?? "Force remove failed.";
|
||||
StatusMessage = result?.Reason ?? Loc.T("vm.worktreesOverview.forceRemoveFailed");
|
||||
return;
|
||||
}
|
||||
if (IsGlobal)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Planning;
|
||||
@@ -14,6 +15,8 @@ public sealed partial class ConflictResolutionViewModel : ObservableObject
|
||||
public string SubtaskTitle { get; }
|
||||
public string TargetBranch { get; }
|
||||
public IReadOnlyList<string> ConflictedFiles { get; }
|
||||
public string SubtaskLabel => Loc.T("vm.conflictResolution.subtaskPrefix", SubtaskTitle);
|
||||
public string TargetLabel => Loc.T("vm.conflictResolution.targetPrefix", TargetBranch);
|
||||
|
||||
[ObservableProperty] private string? _vsCodeError;
|
||||
[ObservableProperty] private string? _actionError;
|
||||
@@ -53,7 +56,7 @@ public sealed partial class ConflictResolutionViewModel : ObservableObject
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
VsCodeError = $"Could not launch VS Code: {ex.Message}. Paths are listed above — copy them manually.";
|
||||
VsCodeError = Loc.T("vm.conflictResolution.vsCodeError", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Planning;
|
||||
@@ -59,7 +60,7 @@ public sealed partial class PlanningDiffViewModel : ObservableObject
|
||||
if (result is null)
|
||||
{
|
||||
DisplayedDiff = "";
|
||||
CombinedWarning = "Could not build combined preview (hub error).";
|
||||
CombinedWarning = Loc.T("vm.planningDiff.hubError");
|
||||
}
|
||||
else if (result.Success)
|
||||
{
|
||||
@@ -69,7 +70,7 @@ public sealed partial class PlanningDiffViewModel : ObservableObject
|
||||
else
|
||||
{
|
||||
var files = result.ConflictedFiles?.Count ?? 0;
|
||||
CombinedWarning = $"Cannot build combined preview: subtask {result.FirstConflictSubtaskId} conflicts with an earlier subtask ({files} files).";
|
||||
CombinedWarning = Loc.T("vm.planningDiff.conflict", result.FirstConflictSubtaskId ?? "", files);
|
||||
DisplayedDiff = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
SelectedItem="{Binding TaskModelSelection, Mode=TwoWay}"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Classes="meta"
|
||||
Text="{Binding EffectiveModelHint, StringFormat='Effective if inherited: {0}'}"
|
||||
Text="{Binding EffectiveModelLabel}"
|
||||
Opacity="0.6"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="meta"
|
||||
Text="{Binding EffectiveAgentHint, StringFormat='Effective if inherited: {0}'}"
|
||||
Text="{Binding EffectiveAgentLabel}"
|
||||
Opacity="0.6"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -47,13 +47,7 @@
|
||||
<!-- Name + machine -->
|
||||
<StackPanel Grid.Column="1" Margin="8,0" Spacing="1" VerticalAlignment="Center">
|
||||
<TextBlock Classes="title" Text="{Binding UserName}"/>
|
||||
<TextBlock Classes="meta">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} / local">
|
||||
<Binding Path="MachineName"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Classes="meta" Text="{Binding MachineNameLocal}"/>
|
||||
</StackPanel>
|
||||
<!-- More button -->
|
||||
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center"
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
<!-- Content -->
|
||||
<StackPanel Spacing="12" Margin="20,16" MinWidth="520">
|
||||
<TextBlock Classes="heading"
|
||||
Text="{Binding SubtaskTitle, StringFormat='Conflicts in subtask: {0}'}"/>
|
||||
<TextBlock Classes="body" Text="{Binding TargetBranch, StringFormat='Merging into: {0}'}"/>
|
||||
Text="{Binding SubtaskLabel}"/>
|
||||
<TextBlock Classes="body" Text="{Binding TargetLabel}"/>
|
||||
<ItemsControl ItemsSource="{Binding ConflictedFiles}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
||||
Reference in New Issue
Block a user