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:
mika kuns
2026-06-03 12:43:30 +02:00
parent 086c6f6c45
commit 350a89f364
23 changed files with 250 additions and 84 deletions

View File

@@ -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 */ }
}