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

@@ -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)

View File

@@ -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.");

View File

@@ -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
{

View File

@@ -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); }
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}
}

View File

@@ -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)