2 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 3aebfa7ec3 release v2.8.1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 07:46:10 +02:00
jpmschweitzerandClaude Opus 4.8 ea340ef141 fix(i18n): localize remaining Claude-pane strings (T-462)
Completes i18n coverage of the conversation surface — the strings the
T-464 migration left as "decorative" English:

- running-indicator verbs (Pondering/Conjuring/… → catalog, Dutch added)
- pane chrome title (primary / secondary N) and banner role
- tool-card titles (Edit → Bewerken) via a tool.name.* key set, with
  command/proper-name tools (Bash, Grep, Glob, LS) falling back to the
  raw name
- collapser step/edit/agent counters
- the folded-activity ticker (result/error/thinking/image + tool name)

English degrades to the placeholder where a key is absent, so untranslated
or MCP tool names render unchanged. en_us and nl_nl stay at key parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:24:34 +02:00
8 changed files with 176 additions and 23 deletions
+9
View File
@@ -38,6 +38,15 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
locale under `assets/i18n/<locale>/`. A new language is a drop-in folder; locale under `assets/i18n/<locale>/`. A new language is a drop-in folder;
en_US behaviour is unchanged. (T-462) en_US behaviour is unchanged. (T-462)
## [2.8.1] — 2026-06-22
### Fixed
- **Localized the remaining Claude-pane strings.** Running-indicator verbs, the
primary/secondary session titles and banner role, tool-card titles, the
step/edit/agent counters, and the folded-activity ticker now resolve through
the catalog — completing i18n coverage of the conversation surface. (T-462)
## [2.7.1] — 2026-06-18 ## [2.7.1] — 2026-06-18
### Changed ### Changed
+40
View File
@@ -6,13 +6,37 @@
"status.primary-exited": { "translation": "session exited — restart clide to retry" }, "status.primary-exited": { "translation": "session exited — restart clide to retry" },
"banner.title": { "translation": "Claude" }, "banner.title": { "translation": "Claude" },
"banner.warmingUp": { "translation": "Warming up — your conversation will appear here." }, "banner.warmingUp": { "translation": "Warming up — your conversation will appear here." },
"banner.role.primary": { "translation": "primary" },
"banner.role.secondary": { "translation": "session {index}" },
"composer.hint": { "translation": "Message Claude… (Enter to send · Shift+Enter for newline)" }, "composer.hint": { "translation": "Message Claude… (Enter to send · Shift+Enter for newline)" },
"composer.stop": { "translation": "Stop ⎋" }, "composer.stop": { "translation": "Stop ⎋" },
"composer.stop.hint": { "translation": "Interrupt the running turn (Escape)" }, "composer.stop.hint": { "translation": "Interrupt the running turn (Escape)" },
"composer.removeAttachment": { "translation": "Remove {name}" }, "composer.removeAttachment": { "translation": "Remove {name}" },
"pane.title.primary": { "translation": "claude — primary" },
"pane.title.secondary": { "translation": "claude — secondary {index}" },
"pane.starting": { "translation": "starting…" }, "pane.starting": { "translation": "starting…" },
"pane.modeBadge.semantics": { "translation": "permission mode: {mode}" }, "pane.modeBadge.semantics": { "translation": "permission mode: {mode}" },
"running.semantics": { "translation": "Claude is running" }, "running.semantics": { "translation": "Claude is running" },
"running.verb.pondering": { "translation": "Pondering" },
"running.verb.conjuring": { "translation": "Conjuring" },
"running.verb.brewing": { "translation": "Brewing" },
"running.verb.tinkering": { "translation": "Tinkering" },
"running.verb.noodling": { "translation": "Noodling" },
"running.verb.percolating": { "translation": "Percolating" },
"running.verb.computing": { "translation": "Computing" },
"running.verb.wrangling": { "translation": "Wrangling" },
"running.verb.untangling": { "translation": "Untangling" },
"running.verb.synthesizing": { "translation": "Synthesizing" },
"running.verb.cogitating": { "translation": "Cogitating" },
"running.verb.whirring": { "translation": "Whirring" },
"running.verb.mincing": { "translation": "Mincing" },
"running.verb.boiling": { "translation": "Boiling" },
"running.verb.humming": { "translation": "Humming" },
"running.verb.buzzing": { "translation": "Buzzing" },
"running.verb.magicking": { "translation": "Magicking" },
"running.verb.cliding": { "translation": "Cliding" },
"running.verb.zooming": { "translation": "Zooming" },
"running.verb.bouncing": { "translation": "Bouncing" },
"conversation.empty": { "translation": "Waiting for Claude…" }, "conversation.empty": { "translation": "Waiting for Claude…" },
"conversation.label.you": { "translation": "you" }, "conversation.label.you": { "translation": "you" },
"conversation.label.claude": { "translation": "claude" }, "conversation.label.claude": { "translation": "claude" },
@@ -39,6 +63,12 @@
"conversation.bashTail.label": { "translation": "live tail" }, "conversation.bashTail.label": { "translation": "live tail" },
"conversation.cluster.activity": { "translation": "Activity" }, "conversation.cluster.activity": { "translation": "Activity" },
"conversation.cluster.edits": { "translation": "Edits" }, "conversation.cluster.edits": { "translation": "Edits" },
"conversation.counter.step": { "translation": "1 step" },
"conversation.counter.steps": { "translation": "{count} steps" },
"conversation.counter.edit": { "translation": "1 edit" },
"conversation.counter.edits": { "translation": "{count} edits" },
"conversation.counter.starting": { "translation": "starting" },
"conversation.counter.agents": { "translation": "{done}/{total} agents" },
"prompt.permission.allow": { "translation": "1. Allow" }, "prompt.permission.allow": { "translation": "1. Allow" },
"prompt.permission.allowRemember": { "translation": "2. Allow & don't ask again" }, "prompt.permission.allowRemember": { "translation": "2. Allow & don't ask again" },
"prompt.permission.deny": { "translation": "{n}. Deny" }, "prompt.permission.deny": { "translation": "{n}. Deny" },
@@ -62,6 +92,16 @@
"tool.edit.before": { "translation": "— before" }, "tool.edit.before": { "translation": "— before" },
"tool.edit.after": { "translation": "+ after" }, "tool.edit.after": { "translation": "+ after" },
"tool.bash.background": { "translation": "background" }, "tool.bash.background": { "translation": "background" },
"tool.name.Read": { "translation": "Read" },
"tool.name.Edit": { "translation": "Edit" },
"tool.name.MultiEdit": { "translation": "MultiEdit" },
"tool.name.Write": { "translation": "Write" },
"tool.name.NotebookEdit": { "translation": "NotebookEdit" },
"tool.name.WebFetch": { "translation": "WebFetch" },
"tool.name.WebSearch": { "translation": "WebSearch" },
"tool.name.Task": { "translation": "Task" },
"tool.name.TodoWrite": { "translation": "TodoWrite" },
"tool.name.ExitPlanMode": { "translation": "ExitPlanMode" },
"permissionControl.semantics": { "translation": "permission mode: {mode}. Activate to change." }, "permissionControl.semantics": { "translation": "permission mode: {mode}. Activate to change." },
"permissionControl.tooltip": { "translation": "Permission mode: {mode} — change (Ctrl/Cmd+M cycles)" }, "permissionControl.tooltip": { "translation": "Permission mode: {mode} — change (Ctrl/Cmd+M cycles)" },
"permissionBadge.tooltip": { "translation": "Permission mode: {mode}. Click to cycle default/acceptEdits/plan; Shift-click for bypassPermissions." }, "permissionBadge.tooltip": { "translation": "Permission mode: {mode}. Click to cycle default/acceptEdits/plan; Shift-click for bypassPermissions." },
+40
View File
@@ -6,13 +6,37 @@
"status.primary-exited": { "translation": "sessie beëindigd — herstart clide om opnieuw te proberen" }, "status.primary-exited": { "translation": "sessie beëindigd — herstart clide om opnieuw te proberen" },
"banner.title": { "translation": "Claude" }, "banner.title": { "translation": "Claude" },
"banner.warmingUp": { "translation": "Opstarten — je gesprek verschijnt hier." }, "banner.warmingUp": { "translation": "Opstarten — je gesprek verschijnt hier." },
"banner.role.primary": { "translation": "primair" },
"banner.role.secondary": { "translation": "sessie {index}" },
"composer.hint": { "translation": "Bericht aan Claude… (Enter om te verzenden · Shift+Enter voor een nieuwe regel)" }, "composer.hint": { "translation": "Bericht aan Claude… (Enter om te verzenden · Shift+Enter voor een nieuwe regel)" },
"composer.stop": { "translation": "Stop ⎋" }, "composer.stop": { "translation": "Stop ⎋" },
"composer.stop.hint": { "translation": "Onderbreek de lopende beurt (Escape)" }, "composer.stop.hint": { "translation": "Onderbreek de lopende beurt (Escape)" },
"composer.removeAttachment": { "translation": "{name} verwijderen" }, "composer.removeAttachment": { "translation": "{name} verwijderen" },
"pane.title.primary": { "translation": "claude — primair" },
"pane.title.secondary": { "translation": "claude — secundair {index}" },
"pane.starting": { "translation": "starten…" }, "pane.starting": { "translation": "starten…" },
"pane.modeBadge.semantics": { "translation": "permissiemodus: {mode}" }, "pane.modeBadge.semantics": { "translation": "permissiemodus: {mode}" },
"running.semantics": { "translation": "Claude is bezig" }, "running.semantics": { "translation": "Claude is bezig" },
"running.verb.pondering": { "translation": "Peinzen" },
"running.verb.conjuring": { "translation": "Toveren" },
"running.verb.brewing": { "translation": "Brouwen" },
"running.verb.tinkering": { "translation": "Knutselen" },
"running.verb.noodling": { "translation": "Prutsen" },
"running.verb.percolating": { "translation": "Pruttelen" },
"running.verb.computing": { "translation": "Rekenen" },
"running.verb.wrangling": { "translation": "Worstelen" },
"running.verb.untangling": { "translation": "Ontwarren" },
"running.verb.synthesizing": { "translation": "Synthetiseren" },
"running.verb.cogitating": { "translation": "Overpeinzen" },
"running.verb.whirring": { "translation": "Snorren" },
"running.verb.mincing": { "translation": "Hakken" },
"running.verb.boiling": { "translation": "Koken" },
"running.verb.humming": { "translation": "Neuriën" },
"running.verb.buzzing": { "translation": "Zoemen" },
"running.verb.magicking": { "translation": "Goochelen" },
"running.verb.cliding": { "translation": "Cliden" },
"running.verb.zooming": { "translation": "Zoeven" },
"running.verb.bouncing": { "translation": "Stuiteren" },
"conversation.empty": { "translation": "Wachten op Claude…" }, "conversation.empty": { "translation": "Wachten op Claude…" },
"conversation.label.you": { "translation": "jij" }, "conversation.label.you": { "translation": "jij" },
"conversation.label.claude": { "translation": "claude" }, "conversation.label.claude": { "translation": "claude" },
@@ -39,6 +63,12 @@
"conversation.bashTail.label": { "translation": "live tail" }, "conversation.bashTail.label": { "translation": "live tail" },
"conversation.cluster.activity": { "translation": "Activiteit" }, "conversation.cluster.activity": { "translation": "Activiteit" },
"conversation.cluster.edits": { "translation": "Wijzigingen" }, "conversation.cluster.edits": { "translation": "Wijzigingen" },
"conversation.counter.step": { "translation": "1 stap" },
"conversation.counter.steps": { "translation": "{count} stappen" },
"conversation.counter.edit": { "translation": "1 wijziging" },
"conversation.counter.edits": { "translation": "{count} wijzigingen" },
"conversation.counter.starting": { "translation": "starten" },
"conversation.counter.agents": { "translation": "{done}/{total} agents" },
"prompt.permission.allow": { "translation": "1. Toestaan" }, "prompt.permission.allow": { "translation": "1. Toestaan" },
"prompt.permission.allowRemember": { "translation": "2. Toestaan en niet meer vragen" }, "prompt.permission.allowRemember": { "translation": "2. Toestaan en niet meer vragen" },
"prompt.permission.deny": { "translation": "{n}. Weigeren" }, "prompt.permission.deny": { "translation": "{n}. Weigeren" },
@@ -62,6 +92,16 @@
"tool.edit.before": { "translation": "— voor" }, "tool.edit.before": { "translation": "— voor" },
"tool.edit.after": { "translation": "+ na" }, "tool.edit.after": { "translation": "+ na" },
"tool.bash.background": { "translation": "achtergrond" }, "tool.bash.background": { "translation": "achtergrond" },
"tool.name.Read": { "translation": "Lezen" },
"tool.name.Edit": { "translation": "Bewerken" },
"tool.name.MultiEdit": { "translation": "Meervoudig bewerken" },
"tool.name.Write": { "translation": "Schrijven" },
"tool.name.NotebookEdit": { "translation": "Notebook bewerken" },
"tool.name.WebFetch": { "translation": "Web ophalen" },
"tool.name.WebSearch": { "translation": "Web zoeken" },
"tool.name.Task": { "translation": "Taak" },
"tool.name.TodoWrite": { "translation": "Takenlijst" },
"tool.name.ExitPlanMode": { "translation": "Planmodus verlaten" },
"permissionControl.semantics": { "translation": "permissiemodus: {mode}. Activeer om te wijzigen." }, "permissionControl.semantics": { "translation": "permissiemodus: {mode}. Activeer om te wijzigen." },
"permissionControl.tooltip": { "translation": "Permissiemodus: {mode} — wijzigen (Ctrl/Cmd+M wisselt)" }, "permissionControl.tooltip": { "translation": "Permissiemodus: {mode} — wijzigen (Ctrl/Cmd+M wisselt)" },
"permissionBadge.tooltip": { "translation": "Permissiemodus: {mode}. Klik om te wisselen tussen default/acceptEdits/plan; Shift-klik voor bypassPermissions." }, "permissionBadge.tooltip": { "translation": "Permissiemodus: {mode}. Klik om te wisselen tussen default/acceptEdits/plan; Shift-klik voor bypassPermissions." },
+1 -1
View File
@@ -39,7 +39,7 @@ self:
# Auto-synced from pubspec.yaml `version:` by `make gen-build-info` # Auto-synced from pubspec.yaml `version:` by `make gen-build-info`
# (runs implicitly on every build/run/test). Don't hand-edit; bump # (runs implicitly on every build/run/test). Don't hand-edit; bump
# pubspec instead. # pubspec instead.
version: "2.8.0" version: "2.8.1"
homepage: https://github.com/postmeridiem/clide homepage: https://github.com/postmeridiem/clide
license: MIT license: MIT
license_file: assets/LICENSE license_file: assets/LICENSE
+18 -2
View File
@@ -728,7 +728,15 @@ class _ClaudePaneState extends State<ClaudePane> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final title = widget.isPrimary ? 'claude — primary' : 'claude — secondary ${widget.secondaryIndex}'; final title = widget.isPrimary
? ClideSettings.i18n.string(context, 'pane.title.primary', namespace: 'builtin.claude', placeholder: 'claude — primary')
: ClideSettings.i18n.interpolated(
context,
'pane.title.secondary',
namespace: 'builtin.claude',
placeholder: 'claude — secondary ${widget.secondaryIndex}',
replacers: [I18nReplacer(from: '{index}', replace: '${widget.secondaryIndex}')],
);
final tokens = ClideSettings.theme.of(context).surface; final tokens = ClideSettings.theme.of(context).surface;
final Widget body; final Widget body;
@@ -761,7 +769,15 @@ class _ClaudePaneState extends State<ClaudePane> {
quietErrorToolUseIds: _session?.quietErrorToolUseIds ?? const <String>{}, quietErrorToolUseIds: _session?.quietErrorToolUseIds ?? const <String>{},
workflows: _session?.workflows ?? const <String, WorkflowRun>{}, workflows: _session?.workflows ?? const <String, WorkflowRun>{},
emptyState: ClaudeBanner( emptyState: ClaudeBanner(
role: widget.isPrimary ? 'primary' : 'session ${widget.secondaryIndex}', role: widget.isPrimary
? ClideSettings.i18n.string(context, 'banner.role.primary', namespace: 'builtin.claude', placeholder: 'primary')
: ClideSettings.i18n.interpolated(
context,
'banner.role.secondary',
namespace: 'builtin.claude',
placeholder: 'session ${widget.secondaryIndex}',
replacers: [I18nReplacer(from: '{index}', replace: '${widget.secondaryIndex}')],
),
workspace: _repoRoot, workspace: _repoRoot,
statusLine: _statusLine, statusLine: _statusLine,
), ),
+60 -17
View File
@@ -800,10 +800,10 @@ class _ConversationTurn extends StatelessWidget {
final outcome = toolUseOutcomes[t.toolUseId]; final outcome = toolUseOutcomes[t.toolUseId];
final color = outcome == null ? tokens.globalFocus : (outcome ? tokens.statusSuccess : tokens.statusError); final color = outcome == null ? tokens.globalFocus : (outcome ? tokens.statusSuccess : tokens.statusError);
final collapser = ClideCollapserCard( final collapser = ClideCollapserCard(
label: t.name, label: _toolNameLabel(context, t.name),
color: color, color: color,
collapsedSummary: _toolUseSummary(t), collapsedSummary: _toolUseSummary(t),
counter: '1 step', counter: _stepsCounter(context, 1),
status: _toolRunStatus(t), status: _toolRunStatus(t),
children: [_toolContentCard(context, t)], children: [_toolContentCard(context, t)],
); );
@@ -818,8 +818,8 @@ class _ConversationTurn extends StatelessWidget {
padding: const EdgeInsets.only(left: 12), padding: const EdgeInsets.only(left: 12),
child: ClideCollapserCard( child: ClideCollapserCard(
label: ClideSettings.i18n.string(context, 'conversation.label.agentRun', namespace: 'builtin.claude', placeholder: 'agent run'), label: ClideSettings.i18n.string(context, 'conversation.label.agentRun', namespace: 'builtin.claude', placeholder: 'agent run'),
collapsedSummary: _summarizeActivity(runItems.last), collapsedSummary: _summarizeActivity(context, runItems.last),
counter: runItems.length == 1 ? '1 step' : '${runItems.length} steps', counter: _stepsCounter(context, runItems.length),
children: [ children: [
for (final r in runItems) for (final r in runItems)
_ConversationTurn( _ConversationTurn(
@@ -849,7 +849,18 @@ class _ConversationTurn extends StatelessWidget {
Widget _workflowCard(BuildContext context, AssistantToolUse t, WorkflowRun run) { Widget _workflowCard(BuildContext context, AssistantToolUse t, WorkflowRun run) {
final title = run.name ?? ClideSettings.i18n.string(context, 'conversation.label.workflow', namespace: 'builtin.claude', placeholder: 'workflow'); final title = run.name ?? ClideSettings.i18n.string(context, 'conversation.label.workflow', namespace: 'builtin.claude', placeholder: 'workflow');
final color = run.done ? tokens.statusSuccess : tokens.globalFocus; final color = run.done ? tokens.statusSuccess : tokens.globalFocus;
final counter = run.agentCount == 0 ? 'starting' : '${run.doneCount}/${run.agentCount} agents'; final counter = run.agentCount == 0
? ClideSettings.i18n.string(context, 'conversation.counter.starting', namespace: 'builtin.claude', placeholder: 'starting')
: ClideSettings.i18n.interpolated(
context,
'conversation.counter.agents',
namespace: 'builtin.claude',
placeholder: '${run.doneCount}/${run.agentCount} agents',
replacers: [
I18nReplacer(from: '{done}', replace: '${run.doneCount}'),
I18nReplacer(from: '{total}', replace: '${run.agentCount}'),
],
);
final detail = run.done ? (run.summary ?? run.description) : run.description; final detail = run.done ? (run.summary ?? run.description) : run.description;
final collapsedSummary = (detail == null || detail == title) ? title : '$title · $detail'; final collapsedSummary = (detail == null || detail == title) ? title : '$title · $detail';
return ClideCollapserCard( return ClideCollapserCard(
@@ -990,7 +1001,7 @@ class _ConversationTurn extends StatelessWidget {
variant: ConversationCardVariant.bordered, variant: ConversationCardVariant.bordered,
accent: accent, accent: accent,
borderColor: outcome == null ? null : accent, borderColor: outcome == null ? null : accent,
label: t.name, label: _toolNameLabel(context, t.name),
copyText: const JsonEncoder.withIndent(' ').convert(t.input), copyText: const JsonEncoder.withIndent(' ').convert(t.input),
status: status, status: status,
body: toolInputBody(context, tokens, t.name, t.input, mono), body: toolInputBody(context, tokens, t.name, t.input, mono),
@@ -1047,7 +1058,7 @@ class _ConversationTurn extends StatelessWidget {
variant: ConversationCardVariant.bordered, variant: ConversationCardVariant.bordered,
accent: quiet ? tokens.globalTextMuted : accent, accent: quiet ? tokens.globalTextMuted : accent,
borderColor: quiet ? tokens.panelBorder : tokens.statusError, borderColor: quiet ? tokens.panelBorder : tokens.statusError,
label: paired != null ? '${paired.name} · $errLabel' : errLabel, label: paired != null ? '${_toolNameLabel(context, paired.name)} · $errLabel' : errLabel,
copyText: t.content, copyText: t.content,
collapsible: quiet || multiline, collapsible: quiet || multiline,
collapsedByDefault: quiet, // genuine errors stay expanded; a denial folds collapsedByDefault: quiet, // genuine errors stay expanded; a denial folds
@@ -1061,7 +1072,7 @@ class _ConversationTurn extends StatelessWidget {
// For Write/Edit, the result is usually "OK" — keep it as plain text. // For Write/Edit, the result is usually "OK" — keep it as plain text.
final multiline = t.content.contains('\n'); final multiline = t.content.contains('\n');
final isOutputTool = paired != null && const {'Bash', 'Read', 'Grep', 'LS'}.contains(paired.name); final isOutputTool = paired != null && const {'Bash', 'Read', 'Grep', 'LS'}.contains(paired.name);
final resultLabel = paired != null ? '${paired.name} · $label' : label; final resultLabel = paired != null ? '${_toolNameLabel(context, paired.name)} · $label' : label;
return ConversationCard( return ConversationCard(
variant: ConversationCardVariant.bordered, variant: ConversationCardVariant.bordered,
accent: accent, accent: accent,
@@ -1127,8 +1138,8 @@ class _ActivityCard extends StatelessWidget {
final count = items.length; final count = items.length;
return ClideCollapserCard( return ClideCollapserCard(
label: ClideSettings.i18n.string(context, 'conversation.cluster.activity', namespace: 'builtin.claude', placeholder: 'Activity'), label: ClideSettings.i18n.string(context, 'conversation.cluster.activity', namespace: 'builtin.claude', placeholder: 'Activity'),
collapsedSummary: _summarizeActivity(items.last), collapsedSummary: _summarizeActivity(context, items.last),
counter: count == 1 ? '1 step' : '$count steps', counter: _stepsCounter(context, count),
status: _runStatus(items, resultByToolUseId), status: _runStatus(items, resultByToolUseId),
children: [ children: [
for (final item in items) for (final item in items)
@@ -1150,6 +1161,35 @@ class _ActivityCard extends StatelessWidget {
} }
} }
/// Localized display label for a tool name (T-462). File/web/task operations
/// have natural translations; command/proper-name tools (Bash, Grep, Glob, LS)
/// have no catalog key and fall back to the raw name via the placeholder.
String _toolNameLabel(BuildContext context, String name) =>
ClideSettings.i18n.string(context, 'tool.name.$name', namespace: 'builtin.claude', placeholder: name);
/// Localized "N steps" counter for a collapser header (T-462). Singular and
/// plural are distinct catalog keys; the English forms double as the fallback.
String _stepsCounter(BuildContext context, int n) => n == 1
? ClideSettings.i18n.string(context, 'conversation.counter.step', namespace: 'builtin.claude', placeholder: '1 step')
: ClideSettings.i18n.interpolated(
context,
'conversation.counter.steps',
namespace: 'builtin.claude',
placeholder: '$n steps',
replacers: [I18nReplacer(from: '{count}', replace: '$n')],
);
/// Localized "N edits" counter for the edit-run collapser header (T-462).
String _editsCounter(BuildContext context, int n) => n == 1
? ClideSettings.i18n.string(context, 'conversation.counter.edit', namespace: 'builtin.claude', placeholder: '1 edit')
: ClideSettings.i18n.interpolated(
context,
'conversation.counter.edits',
namespace: 'builtin.claude',
placeholder: '$n edits',
replacers: [I18nReplacer(from: '{count}', replace: '$n')],
);
/// Aggregate live status for a run's header tick (T-296): error if any tool in /// Aggregate live status for a run's header tick (T-296): error if any tool in
/// the run failed, else running while its last tool awaits a result, else /// the run failed, else running while its last tool awaits a result, else
/// success. Null (no tools) shows no indicator. /// success. Null (no tools) shows no indicator.
@@ -1193,8 +1233,8 @@ class _EditRunCard extends StatelessWidget {
final count = edits.length; final count = edits.length;
return ClideCollapserCard( return ClideCollapserCard(
label: ClideSettings.i18n.string(context, 'conversation.cluster.edits', namespace: 'builtin.claude', placeholder: 'Edits'), label: ClideSettings.i18n.string(context, 'conversation.cluster.edits', namespace: 'builtin.claude', placeholder: 'Edits'),
collapsedSummary: _summarizeActivity(edits.last), collapsedSummary: _summarizeActivity(context, edits.last),
counter: count == 1 ? '1 edit' : '$count edits', counter: _editsCounter(context, count),
status: _runStatus(edits, resultByToolUseId), status: _runStatus(edits, resultByToolUseId),
children: [ children: [
for (final item in edits) for (final item in edits)
@@ -1216,22 +1256,25 @@ class _EditRunCard extends StatelessWidget {
} }
/// One-line summary of a folded item for the collapsed ticker. /// One-line summary of a folded item for the collapsed ticker.
String _summarizeActivity(ConversationItem item) { String _summarizeActivity(BuildContext context, ConversationItem item) {
String label(String key, String fallback) => ClideSettings.i18n.string(context, key, namespace: 'builtin.claude', placeholder: fallback);
switch (item) { switch (item) {
case AssistantToolUse(:final name, :final input): case AssistantToolUse(:final name, :final input):
final raw = input['command'] ?? input['file_path'] ?? input['path'] ?? input['pattern'] ?? input['url']; final raw = input['command'] ?? input['file_path'] ?? input['path'] ?? input['pattern'] ?? input['url'];
final detail = raw is String ? raw.split('\n').first.trim() : ''; final detail = raw is String ? raw.split('\n').first.trim() : '';
final clipped = detail.length > 72 ? '${detail.substring(0, 72)}' : detail; final clipped = detail.length > 72 ? '${detail.substring(0, 72)}' : detail;
return clipped.isEmpty ? name : '$name $clipped'; final toolName = _toolNameLabel(context, name);
return clipped.isEmpty ? toolName : '$toolName $clipped';
case ToolResultMessage(:final isError): case ToolResultMessage(:final isError):
return isError ? '↳ result · error' : '↳ result'; final result = label('conversation.label.result', 'result');
return isError ? '$result · ${label('conversation.label.error', 'error')}' : '$result';
case AssistantThinkingMessage(): case AssistantThinkingMessage():
return 'thinking'; return '${label('conversation.label.thinking', 'thinking')}';
case UserMessage(:final text): case UserMessage(:final text):
return text; return text;
case AssistantTextMessage(:final text): case AssistantTextMessage(:final text):
return text; return text;
case ImageMessage(:final path): case ImageMessage(:final path):
return 'image $path'; return '${label('conversation.label.image', 'image')} $path';
} }
} }
@@ -90,17 +90,22 @@ class _RunningIndicatorState extends State<RunningIndicator> with SingleTickerPr
label: ClideSettings.i18n.string(context, 'running.semantics', namespace: 'builtin.claude', placeholder: 'Claude is running'), label: ClideSettings.i18n.string(context, 'running.semantics', namespace: 'builtin.claude', placeholder: 'Claude is running'),
child: ExcludeSemantics( child: ExcludeSemantics(
child: reduced child: reduced
? ClideText('${_verbs.first}', color: claudeAccent, fontSize: clideFontMeta) ? ClideText('${_verb(context, _verbs.first)}', color: claudeAccent, fontSize: clideFontMeta)
: AnimatedBuilder( : AnimatedBuilder(
animation: _c, animation: _c,
builder: (ctx, _) { builder: (ctx, _) {
final elapsed = _c.value * _periodSeconds; final elapsed = _c.value * _periodSeconds;
final dots = '.' * (elapsed.floor() % 4); final dots = '.' * (elapsed.floor() % 4);
final word = _verbs[(elapsed ~/ _secondsPerWord) % _verbs.length]; final word = _verbs[(elapsed ~/ _secondsPerWord) % _verbs.length];
return ClideText('$word$dots', color: claudeAccent, fontSize: clideFontMeta); return ClideText('${_verb(ctx, word)}$dots', color: claudeAccent, fontSize: clideFontMeta);
}, },
), ),
), ),
); );
} }
/// Localize a verb through the catalog; the English word doubles as the key
/// suffix and the fallback, so a missing translation degrades to English.
String _verb(BuildContext context, String word) =>
ClideSettings.i18n.string(context, 'running.verb.${word.toLowerCase()}', namespace: 'builtin.claude', placeholder: word);
} }
+1 -1
View File
@@ -13,7 +13,7 @@ description: >-
subsystem handlers (pane, files, editor, git, pql), and the subsystem handlers (pane, files, editor, git, pql), and the
extension framework. extension framework.
publish_to: none publish_to: none
version: 2.8.0 version: 2.8.1
repository: https://github.com/postmeridiem/clide repository: https://github.com/postmeridiem/clide
# Short user-facing tagline (the welcome subtitle, web meta # Short user-facing tagline (the welcome subtitle, web meta
# description, etc.). Baked into lib/src/build_info.g.dart by # description, etc.). Baked into lib/src/build_info.g.dart by