claude-ws 0.3.97 → 0.3.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/locales/de.json +374 -12
  2. package/locales/en.json +374 -12
  3. package/locales/es.json +398 -11
  4. package/locales/fr.json +398 -11
  5. package/locales/ja.json +398 -11
  6. package/locales/ko.json +398 -11
  7. package/locales/vi.json +374 -12
  8. package/locales/zh.json +398 -11
  9. package/package.json +1 -1
  10. package/server.ts +283 -6
  11. package/src/app/[locale]/not-found.tsx +6 -3
  12. package/src/app/[locale]/page.tsx +14 -4
  13. package/src/app/api/attempts/[id]/workflow/route.ts +76 -0
  14. package/src/app/api/questions/answer/route.ts +58 -0
  15. package/src/app/api/questions/route.ts +68 -0
  16. package/src/app/api/tasks/[id]/compact/route.ts +62 -0
  17. package/src/components/access-anywhere/api-access-key-setup-modal.tsx +2 -2
  18. package/src/components/access-anywhere/tunnel-settings-dialog.tsx +6 -6
  19. package/src/components/access-anywhere/wizard-step-ctunnel.tsx +8 -8
  20. package/src/components/agent-factory/dependency-tree.tsx +5 -3
  21. package/src/components/agent-factory/discovery-dialog.tsx +26 -22
  22. package/src/components/agent-factory/plugin-detail-dialog.tsx +41 -38
  23. package/src/components/agent-factory/plugin-form-dialog.tsx +23 -20
  24. package/src/components/agent-factory/plugin-list.tsx +20 -17
  25. package/src/components/agent-factory/upload-dialog.tsx +17 -14
  26. package/src/components/auth/agent-provider-dialog.tsx +67 -65
  27. package/src/components/auth/api-key-dialog.tsx +14 -11
  28. package/src/components/auth/auth-error-message.tsx +6 -3
  29. package/src/components/editor/code-editor-with-inline-edit.tsx +4 -2
  30. package/src/components/editor/file-diff-resolver-modal.tsx +31 -26
  31. package/src/components/editor/inline-edit-dialog.tsx +9 -6
  32. package/src/components/editor/selection-mention-popup.tsx +3 -1
  33. package/src/components/header/project-selector.tsx +7 -4
  34. package/src/components/header.tsx +70 -4
  35. package/src/components/kanban/column.tsx +11 -0
  36. package/src/components/kanban/task-card.tsx +70 -4
  37. package/src/components/project-settings/component-selector.tsx +3 -1
  38. package/src/components/project-settings/plugin-upload-dialog.tsx +7 -5
  39. package/src/components/project-settings/project-settings-dialog.tsx +5 -3
  40. package/src/components/questions/questions-panel.tsx +136 -0
  41. package/src/components/settings/folder-browser-dialog.tsx +29 -25
  42. package/src/components/settings/settings-page.tsx +64 -18
  43. package/src/components/settings/setup-dialog.tsx +26 -23
  44. package/src/components/setup/unified-setup-wizard.tsx +12 -9
  45. package/src/components/sidebar/file-browser/file-create-buttons.tsx +7 -3
  46. package/src/components/sidebar/file-browser/file-tab-content.tsx +19 -15
  47. package/src/components/sidebar/file-browser/file-tabs-panel.tsx +7 -4
  48. package/src/components/sidebar/file-browser/file-tree.tsx +3 -1
  49. package/src/components/sidebar/git-changes/branch-checkout-modal.tsx +6 -4
  50. package/src/components/sidebar/git-changes/commit-details-modal.tsx +5 -3
  51. package/src/components/sidebar/git-changes/diff-tabs-panel.tsx +3 -1
  52. package/src/components/sidebar/git-changes/git-file-item.tsx +8 -6
  53. package/src/components/sidebar/git-changes/git-graph.tsx +8 -5
  54. package/src/components/sidebar/git-changes/git-panel.tsx +28 -27
  55. package/src/components/sidebar/git-changes/git-section.tsx +5 -3
  56. package/src/components/sidebar/shells/shell-panel.tsx +3 -1
  57. package/src/components/task/attachment-bar.tsx +4 -1
  58. package/src/components/task/attempt-item.tsx +7 -5
  59. package/src/components/task/conversation-view.tsx +21 -13
  60. package/src/components/task/floating-chat-window.tsx +14 -5
  61. package/src/components/task/interactive-command/checkpoint-list.tsx +5 -3
  62. package/src/components/task/interactive-command/confirm-dialog.tsx +9 -4
  63. package/src/components/task/interactive-command/interactive-command-overlay.tsx +23 -9
  64. package/src/components/task/interactive-command/question-prompt.tsx +12 -8
  65. package/src/components/task/pending-question-indicator.tsx +5 -3
  66. package/src/components/task/prompt-input.tsx +1 -1
  67. package/src/components/task/shell-log-view.tsx +3 -1
  68. package/src/components/task/status-line.tsx +84 -23
  69. package/src/components/task/task-detail-panel.tsx +27 -27
  70. package/src/components/task/task-shell-indicator.tsx +10 -6
  71. package/src/components/terminal/terminal-context-menu.tsx +6 -4
  72. package/src/components/terminal/terminal-instance.tsx +11 -3
  73. package/src/components/terminal/terminal-panel.tsx +6 -3
  74. package/src/components/terminal/terminal-shortcut-bar.tsx +3 -1
  75. package/src/components/terminal/terminal-tab-bar.tsx +5 -3
  76. package/src/components/workflow/workflow-panel.tsx +181 -0
  77. package/src/hooks/use-attempt-stream.ts +96 -3
  78. package/src/lib/agent-manager.ts +89 -3
  79. package/src/lib/db/index.ts +18 -0
  80. package/src/lib/db/schema.ts +29 -0
  81. package/src/lib/process-manager.ts +28 -7
  82. package/src/lib/session-manager.ts +60 -0
  83. package/src/lib/usage-tracker.ts +19 -19
  84. package/src/lib/workflow-tracker.ts +118 -20
  85. package/src/stores/questions-store.ts +76 -0
  86. package/src/stores/workflow-store.ts +71 -0
@@ -1,19 +1,43 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { ArrowLeft, FolderOpen, X, Bot, Shield, Check } from 'lucide-react';
5
6
  import { Button } from '@/components/ui/button';
6
7
  import { Input } from '@/components/ui/input';
8
+ import { Checkbox } from '@/components/ui/checkbox';
7
9
  import { useProjectStore } from '@/stores/project-store';
8
10
  import { useSettingsUIStore } from '@/stores/settings-ui-store';
9
11
  import { dispatchAgentProviderConfig } from '@/components/auth/agent-provider-dialog';
10
12
  import { ApiAccessKeySetupForm } from '@/components/access-anywhere/api-access-key-setup-modal';
11
13
 
12
14
  export function SettingsPage() {
15
+ const t = useTranslations('settings');
16
+ const tCommon = useTranslations('common');
13
17
  const { currentProject, updateProject } = useProjectStore();
14
18
  const { setOpen: setSettingsOpen } = useSettingsUIStore();
15
19
  const [editingName, setEditingName] = useState('');
16
20
  const [isEditing, setIsEditing] = useState(false);
21
+ const [autoCompactEnabled, setAutoCompactEnabled] = useState(false);
22
+
23
+ useEffect(() => {
24
+ fetch('/api/settings?keys=auto_compact_enabled')
25
+ .then((res) => res.json())
26
+ .then((data) => {
27
+ if (data.auto_compact_enabled === 'true') {
28
+ setAutoCompactEnabled(true);
29
+ }
30
+ });
31
+ }, []);
32
+
33
+ const handleAutoCompactToggle = async (checked: boolean) => {
34
+ setAutoCompactEnabled(checked);
35
+ await fetch('/api/settings', {
36
+ method: 'POST',
37
+ headers: { 'Content-Type': 'application/json' },
38
+ body: JSON.stringify({ key: 'auto_compact_enabled', value: String(checked) }),
39
+ });
40
+ };
17
41
 
18
42
  const [agentProviderConfigured, setAgentProviderConfigured] = useState(false);
19
43
  const [apiAccessKeyConfigured, setApiAccessKeyConfigured] = useState(false);
@@ -71,7 +95,7 @@ export function SettingsPage() {
71
95
  </Button>
72
96
  <div className="flex items-center gap-3">
73
97
  <FolderOpen className="w-6 h-6" />
74
- <h1 className="text-2xl font-bold">Settings</h1>
98
+ <h1 className="text-2xl font-bold">{t('title')}</h1>
75
99
  </div>
76
100
  </div>
77
101
  <Button
@@ -89,7 +113,7 @@ export function SettingsPage() {
89
113
  {/* Current Project Section */}
90
114
  {currentProject && (
91
115
  <div className="space-y-4">
92
- <h2 className="text-lg font-semibold">Current Project</h2>
116
+ <h2 className="text-lg font-semibold">{t('currentProject')}</h2>
93
117
  <div className="space-y-3 p-4 border rounded-lg bg-card">
94
118
  <div className="flex items-center gap-3">
95
119
  <FolderOpen className="h-5 w-5 text-muted-foreground" />
@@ -102,14 +126,14 @@ export function SettingsPage() {
102
126
  autoFocus
103
127
  />
104
128
  <Button size="sm" onClick={handleSaveName}>
105
- Save
129
+ {tCommon('save')}
106
130
  </Button>
107
131
  <Button
108
132
  size="sm"
109
133
  variant="ghost"
110
134
  onClick={() => setIsEditing(false)}
111
135
  >
112
- Cancel
136
+ {tCommon('cancel')}
113
137
  </Button>
114
138
  </div>
115
139
  ) : (
@@ -123,7 +147,7 @@ export function SettingsPage() {
123
147
  setIsEditing(true);
124
148
  }}
125
149
  >
126
- Edit
150
+ {tCommon('edit')}
127
151
  </Button>
128
152
  </div>
129
153
  )}
@@ -135,21 +159,43 @@ export function SettingsPage() {
135
159
  </div>
136
160
  )}
137
161
 
162
+ {/* Context Management Section */}
163
+ <div className="space-y-4">
164
+ <h2 className="text-lg font-semibold">Context Management</h2>
165
+ <div className="space-y-3 p-4 border rounded-lg bg-card">
166
+ <div className="flex items-start gap-3">
167
+ <Checkbox
168
+ id="auto-compact"
169
+ checked={autoCompactEnabled}
170
+ onCheckedChange={(checked) => handleAutoCompactToggle(checked === true)}
171
+ />
172
+ <div className="space-y-1">
173
+ <label htmlFor="auto-compact" className="font-medium leading-none cursor-pointer">
174
+ Auto-compact conversations
175
+ </label>
176
+ <p className="text-sm text-muted-foreground">
177
+ Automatically compact conversation context when it exceeds 75% of the context window
178
+ </p>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
138
184
  {/* Agent Provider Section */}
139
185
  <div className="space-y-4">
140
- <h2 className="text-lg font-semibold">Agent Provider</h2>
186
+ <h2 className="text-lg font-semibold">{tCommon('agentProvider')}</h2>
141
187
  <div className="p-4 border rounded-lg bg-card">
142
188
  <div className="flex items-center justify-between">
143
189
  <div className="flex items-center gap-3">
144
190
  <Bot className="h-5 w-5 text-muted-foreground" />
145
191
  <div>
146
- <p className="font-medium">Claude API Configuration</p>
192
+ <p className="font-medium">{t('claudeApiConfig')}</p>
147
193
  <p className="text-sm text-muted-foreground">
148
194
  {loadingStatus
149
- ? 'Checking...'
195
+ ? tCommon('checking')
150
196
  : agentProviderConfigured
151
- ? 'A provider is configured'
152
- : 'No provider configured'
197
+ ? t('providerConfigured')
198
+ : t('noProviderConfigured')
153
199
  }
154
200
  </p>
155
201
  </div>
@@ -158,7 +204,7 @@ export function SettingsPage() {
158
204
  {!loadingStatus && agentProviderConfigured && (
159
205
  <span className="inline-flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
160
206
  <Check className="h-3 w-3" />
161
- Configured
207
+ {tCommon('configured')}
162
208
  </span>
163
209
  )}
164
210
  <Button
@@ -166,7 +212,7 @@ export function SettingsPage() {
166
212
  size="sm"
167
213
  onClick={() => dispatchAgentProviderConfig()}
168
214
  >
169
- Configure
215
+ {t('configure')}
170
216
  </Button>
171
217
  </div>
172
218
  </div>
@@ -175,19 +221,19 @@ export function SettingsPage() {
175
221
 
176
222
  {/* API Access Key Section */}
177
223
  <div className="space-y-4">
178
- <h2 className="text-lg font-semibold">API Access Key</h2>
224
+ <h2 className="text-lg font-semibold">{t('apiAccessKey')}</h2>
179
225
  <div className="p-4 border rounded-lg bg-card space-y-4">
180
226
  <div className="flex items-center justify-between">
181
227
  <div className="flex items-center gap-3">
182
228
  <Shield className="h-5 w-5 text-muted-foreground" />
183
229
  <div>
184
- <p className="font-medium">Remote Access Authentication</p>
230
+ <p className="font-medium">{t('remoteAccessAuth')}</p>
185
231
  <p className="text-sm text-muted-foreground">
186
232
  {loadingStatus
187
- ? 'Checking...'
233
+ ? tCommon('checking')
188
234
  : apiAccessKeyConfigured
189
- ? 'An API access key is configured'
190
- : 'No API access key configured'
235
+ ? t('apiKeyConfigured')
236
+ : t('noApiKeyConfigured')
191
237
  }
192
238
  </p>
193
239
  </div>
@@ -195,7 +241,7 @@ export function SettingsPage() {
195
241
  {!loadingStatus && apiAccessKeyConfigured && (
196
242
  <span className="inline-flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
197
243
  <Check className="h-3 w-3" />
198
- Configured
244
+ {tCommon('configured')}
199
245
  </span>
200
246
  )}
201
247
  </div>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { FolderOpen, AlertCircle, Plus, FolderOpen as FolderOpenIcon, Folder } from 'lucide-react';
5
6
  import {
6
7
  Dialog,
@@ -25,6 +26,8 @@ interface SetupDialogProps {
25
26
  }
26
27
 
27
28
  export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
29
+ const t = useTranslations('settings');
30
+ const tCommon = useTranslations('common');
28
31
  const { createProject, setCurrentProject } = useProjectStore();
29
32
  const [mode, setMode] = useState<Mode>('open');
30
33
  const [name, setName] = useState('');
@@ -68,7 +71,7 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
68
71
  setError('');
69
72
 
70
73
  if (!name.trim()) {
71
- setError('Project name is required');
74
+ setError(t('projectNameRequired'));
72
75
  return;
73
76
  }
74
77
 
@@ -77,26 +80,26 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
77
80
  // For create mode, build path from root + sanitized name
78
81
  if (mode === 'create') {
79
82
  if (!rootPath.trim()) {
80
- setError('Root folder is required');
83
+ setError(t('rootFolderRequired'));
81
84
  return;
82
85
  }
83
86
  const sanitizedName = sanitizeDirName(name);
84
87
  if (!sanitizedName) {
85
- setError('Project name must contain at least one alphanumeric character');
88
+ setError(t('projectNameAlphanumeric'));
86
89
  return;
87
90
  }
88
91
  finalPath = `${rootPath.trim()}/${sanitizedName}`;
89
92
  } else {
90
93
  // Open mode - path is required
91
94
  if (!path.trim()) {
92
- setError('Project path is required');
95
+ setError(t('projectPathRequired'));
93
96
  return;
94
97
  }
95
98
  }
96
99
 
97
100
  // Validate path format
98
101
  if (!finalPath.startsWith('/') && !finalPath.match(/^[A-Za-z]:\\/)) {
99
- setError('Please enter an absolute path');
102
+ setError(t('enterAbsolutePath'));
100
103
  return;
101
104
  }
102
105
 
@@ -122,11 +125,11 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
122
125
  <Dialog open={open} onOpenChange={onOpenChange}>
123
126
  <DialogContent className="sm:max-w-[500px]">
124
127
  <DialogHeader>
125
- <DialogTitle>Set Up Project</DialogTitle>
128
+ <DialogTitle>{t('setUpProject')}</DialogTitle>
126
129
  <DialogDescription>
127
130
  {mode === 'open'
128
- ? 'Select an existing project folder to open.'
129
- : 'Configure a project folder to use with Claude Code.'}
131
+ ? t('selectExistingDescription')
132
+ : t('configureNewDescription')}
130
133
  </DialogDescription>
131
134
  </DialogHeader>
132
135
 
@@ -137,14 +140,14 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
137
140
  className="[&[data-state=active]]:![background-color:rgba(255,255,255,0.2)]"
138
141
  >
139
142
  <FolderOpenIcon className="h-4 w-4" />
140
- Open Existing
143
+ {t('openExisting')}
141
144
  </TabsTrigger>
142
145
  <TabsTrigger
143
146
  value="create"
144
147
  className="[&[data-state=active]]:![background-color:rgba(255,255,255,0.2)]"
145
148
  >
146
149
  <Plus className="h-4 w-4" />
147
- Create New
150
+ {t('createNew')}
148
151
  </TabsTrigger>
149
152
  </TabsList>
150
153
 
@@ -153,7 +156,7 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
153
156
  {/* Project Path - auto-named from folder */}
154
157
  <div className="space-y-2">
155
158
  <label htmlFor="path-open" className="text-sm font-medium">
156
- Project Folder
159
+ {t('projectFolder')}
157
160
  </label>
158
161
  <div className="flex gap-2">
159
162
  <div
@@ -177,12 +180,12 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
177
180
  onClick={() => (setBrowserMode('project'), setFolderBrowserOpen(true))}
178
181
  disabled={loading}
179
182
  >
180
- Browse
183
+ {tCommon('browse')}
181
184
  </Button>
182
185
  </div>
183
186
  {path && (
184
187
  <p className="text-xs text-muted-foreground">
185
- Project name: <span className="font-medium">{name || '(auto-detected)'}</span>
188
+ {t('projectName')}: <span className="font-medium">{name || t('autoDetected')}</span>
186
189
  </p>
187
190
  )}
188
191
  </div>
@@ -203,10 +206,10 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
203
206
  onClick={() => onOpenChange(false)}
204
207
  disabled={loading}
205
208
  >
206
- Cancel
209
+ {tCommon('cancel')}
207
210
  </Button>
208
211
  <Button type="submit" disabled={loading || !path}>
209
- {loading ? 'Opening...' : 'Open Project'}
212
+ {loading ? tCommon('opening') : t('openProject')}
210
213
  </Button>
211
214
  </div>
212
215
  </form>
@@ -217,7 +220,7 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
217
220
  {/* Project Name */}
218
221
  <div className="space-y-2">
219
222
  <label htmlFor="name" className="text-sm font-medium">
220
- Project Name
223
+ {t('projectName')}
221
224
  </label>
222
225
  <Input
223
226
  id="name"
@@ -227,14 +230,14 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
227
230
  disabled={loading}
228
231
  />
229
232
  <p className="text-xs text-muted-foreground">
230
- This will be the project folder name
233
+ {t('folderNameHint')}
231
234
  </p>
232
235
  </div>
233
236
 
234
237
  {/* Root Folder */}
235
238
  <div className="space-y-2">
236
239
  <label htmlFor="root-path" className="text-sm font-medium">
237
- Root Folder
240
+ {t('rootFolder')}
238
241
  </label>
239
242
  <div className="flex gap-2">
240
243
  <div
@@ -258,11 +261,11 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
258
261
  onClick={() => (setBrowserMode('root'), setFolderBrowserOpen(true))}
259
262
  disabled={loading}
260
263
  >
261
- Browse
264
+ {tCommon('browse')}
262
265
  </Button>
263
266
  </div>
264
267
  <p className="text-xs text-muted-foreground">
265
- Select the parent folder where project will be created
268
+ {t('selectParentFolder')}
266
269
  </p>
267
270
  </div>
268
271
 
@@ -270,7 +273,7 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
270
273
  {fullProjectPath && (
271
274
  <div className="space-y-1">
272
275
  <label className="text-sm font-medium">
273
- Project will be created at:
276
+ {t('projectCreatedAt')}
274
277
  </label>
275
278
  <div className="p-3 bg-muted rounded-md text-sm font-mono break-all">
276
279
  {fullProjectPath}
@@ -294,10 +297,10 @@ export function SetupDialog({ open, onOpenChange }: SetupDialogProps) {
294
297
  onClick={() => onOpenChange(false)}
295
298
  disabled={loading}
296
299
  >
297
- Cancel
300
+ {tCommon('cancel')}
298
301
  </Button>
299
302
  <Button type="submit" disabled={loading || !name || !rootPath}>
300
- {loading ? 'Creating...' : 'Create Project'}
303
+ {loading ? tCommon('creating') : t('createProject')}
301
304
  </Button>
302
305
  </div>
303
306
  </form>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import {
5
6
  Dialog,
6
7
  DialogContent,
@@ -32,6 +33,8 @@ interface UnifiedSetupWizardProps {
32
33
  }
33
34
 
34
35
  export function UnifiedSetupWizard({ open, onOpenChange, initialStatus }: UnifiedSetupWizardProps) {
36
+ const t = useTranslations('settings');
37
+ const tCommon = useTranslations('common');
35
38
  const [expandedSection, setExpandedSection] = useState<SectionId | null>(null);
36
39
  const [status, setStatus] = useState<SectionStatus>({
37
40
  agentProvider: initialStatus?.agentProvider ?? false,
@@ -118,9 +121,9 @@ export function UnifiedSetupWizard({ open, onOpenChange, initialStatus }: Unifie
118
121
  <Dialog open={open} onOpenChange={handleClose}>
119
122
  <DialogContent className="sm:max-w-[600px] z-[9999] max-h-[80vh] p-0 flex flex-col overflow-hidden">
120
123
  <DialogHeader className="p-6 pb-2 shrink-0">
121
- <DialogTitle>Set Up Your Workspace</DialogTitle>
124
+ <DialogTitle>{t('setUpWorkspace')}</DialogTitle>
122
125
  <DialogDescription>
123
- Configure the essential settings for your workspace. You can always change these later in Settings.
126
+ {t('setUpWorkspaceDescription')}
124
127
  </DialogDescription>
125
128
  </DialogHeader>
126
129
 
@@ -139,9 +142,9 @@ export function UnifiedSetupWizard({ open, onOpenChange, initialStatus }: Unifie
139
142
  {status.agentProvider ? <Check className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
140
143
  </div>
141
144
  <div>
142
- <span className="font-medium">Agent Provider</span>
145
+ <span className="font-medium">{tCommon('agentProvider')}</span>
143
146
  <p className="text-xs text-muted-foreground">
144
- {status.agentProvider ? 'Configured' : 'Configure how to authenticate with Claude API'}
147
+ {status.agentProvider ? tCommon('configured') : t('configureAuthDescription')}
145
148
  </p>
146
149
  </div>
147
150
  </div>
@@ -171,9 +174,9 @@ export function UnifiedSetupWizard({ open, onOpenChange, initialStatus }: Unifie
171
174
  {status.apiAccessKey ? <Check className="h-4 w-4" /> : <Shield className="h-4 w-4" />}
172
175
  </div>
173
176
  <div>
174
- <span className="font-medium">API Access Key</span>
177
+ <span className="font-medium">{t('apiAccessKey')}</span>
175
178
  <p className="text-xs text-muted-foreground">
176
- {status.apiAccessKey ? 'Configured' : 'Set up authentication for remote access'}
179
+ {status.apiAccessKey ? tCommon('configured') : t('setUpRemoteAccess')}
177
180
  </p>
178
181
  </div>
179
182
  </div>
@@ -203,9 +206,9 @@ export function UnifiedSetupWizard({ open, onOpenChange, initialStatus }: Unifie
203
206
  {status.remoteAccess ? <Check className="h-4 w-4" /> : <Globe className="h-4 w-4" />}
204
207
  </div>
205
208
  <div>
206
- <span className="font-medium">Remote Access</span>
209
+ <span className="font-medium">{tCommon('accessAnywhere')}</span>
207
210
  <p className="text-xs text-muted-foreground">
208
- {status.remoteAccess ? 'Configured' : 'Access your workspace from anywhere'}
211
+ {status.remoteAccess ? tCommon('configured') : t('setUpRemoteAccess')}
209
212
  </p>
210
213
  </div>
211
214
  </div>
@@ -242,7 +245,7 @@ export function UnifiedSetupWizard({ open, onOpenChange, initialStatus }: Unifie
242
245
  </Label>
243
246
  </div>
244
247
  <Button variant="outline" onClick={handleClose}>
245
- Close
248
+ {tCommon('close')}
246
249
  </Button>
247
250
  </div>
248
251
  </DialogContent>
@@ -17,6 +17,7 @@ import { toast } from 'sonner';
17
17
  import { useSidebarStore } from '@/stores/sidebar-store';
18
18
  import { FileUploadDialog } from './file-upload-dialog';
19
19
  import type { FileEntry } from '@/types';
20
+ import { useTranslations } from 'next-intl';
20
21
 
21
22
  interface FileCreateButtonsProps {
22
23
  /** Parent directory entry where files/folders will be created */
@@ -32,6 +33,9 @@ interface FileCreateButtonsProps {
32
33
  * Displays at the bottom of file tree for quick creation at project root
33
34
  */
34
35
  export function FileCreateButtons({ entry, rootPath, onRefresh }: FileCreateButtonsProps) {
36
+ const tSidebar = useTranslations('sidebar');
37
+ const tSettings = useTranslations('settings');
38
+ const tCommon = useTranslations('common');
35
39
  const [createDialogOpen, setCreateDialogOpen] = useState(false);
36
40
  const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
37
41
  const [createType, setCreateType] = useState<'file' | 'folder'>('file');
@@ -65,7 +69,7 @@ export function FileCreateButtons({ entry, rootPath, onRefresh }: FileCreateButt
65
69
  const handleCreate = async () => {
66
70
  const trimmedName = createName.trim();
67
71
  if (!trimmedName) {
68
- toast.error('Name cannot be empty');
72
+ toast.error(tSidebar('nameCannotBeEmpty'));
69
73
  return;
70
74
  }
71
75
 
@@ -84,7 +88,7 @@ export function FileCreateButtons({ entry, rootPath, onRefresh }: FileCreateButt
84
88
 
85
89
  if (!res.ok) {
86
90
  const data = await res.json();
87
- throw new Error(data.error || 'Create failed');
91
+ throw new Error(data.error || tSettings('createFailed'));
88
92
  }
89
93
 
90
94
  const data = await res.json();
@@ -103,7 +107,7 @@ export function FileCreateButtons({ entry, rootPath, onRefresh }: FileCreateButt
103
107
  openTab(data.path);
104
108
  }
105
109
  } catch (err) {
106
- toast.error(err instanceof Error ? err.message : 'Create failed');
110
+ toast.error(err instanceof Error ? err.message : tSettings('createFailed'));
107
111
  } finally {
108
112
  setIsCreating(false);
109
113
  }
@@ -14,6 +14,7 @@ import { useContextMentionStore } from '@/stores/context-mention-store';
14
14
  import { useProjectStore } from '@/stores/project-store';
15
15
  import { useFileSync } from '@/hooks/use-file-sync';
16
16
  import { waitForElement } from '@/lib/utils';
17
+ import { useTranslations } from 'next-intl';
17
18
 
18
19
  interface FileContent {
19
20
  content: string | null;
@@ -30,6 +31,9 @@ interface FileTabContentProps {
30
31
 
31
32
  export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
32
33
  const activeProject = useActiveProject();
34
+ const t = useTranslations('editor');
35
+ const tCommon = useTranslations('common');
36
+ const tSidebar = useTranslations('sidebar');
33
37
  const { editorPosition, setEditorPosition, updateTabDirty, pendingEditorPosition, clearPendingEditorPosition } = useSidebarStore();
34
38
  const { selectedTask, tasks, createTask, selectTask, setSelectedTask } = useTaskStore();
35
39
  const { addFileMention, addLineMention } = useContextMentionStore();
@@ -439,7 +443,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
439
443
  if (createNew || !targetTask) {
440
444
  const projectId = selectedProjectIds[0];
441
445
  if (!projectId) {
442
- alert('No project selected');
446
+ alert(tSidebar('selectProject'));
443
447
  return;
444
448
  }
445
449
 
@@ -470,7 +474,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
470
474
  }
471
475
  } catch (error) {
472
476
  console.error('Failed to attach file:', error);
473
- alert(error instanceof Error ? error.message : 'Failed to attach file to chat');
477
+ alert(error instanceof Error ? error.message : t('addFileToChat'));
474
478
  }
475
479
  };
476
480
 
@@ -498,7 +502,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
498
502
  variant="ghost"
499
503
  size="icon-sm"
500
504
  onClick={() => setSearchVisible(!searchVisible)}
501
- title="Search in file (⌘F)"
505
+ title={t('searchPlaceholder') + ' (⌘F)'}
502
506
  className={searchVisible ? 'bg-accent' : ''}
503
507
  >
504
508
  <Search className="size-4" />
@@ -512,7 +516,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
512
516
  size="icon-sm"
513
517
  onClick={handleUndo}
514
518
  disabled={!canUndo}
515
- title="Undo (⌘Z)"
519
+ title={t('undo') + ' (⌘Z)'}
516
520
  >
517
521
  <Undo className="size-4" />
518
522
  </Button>
@@ -521,7 +525,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
521
525
  size="icon-sm"
522
526
  onClick={handleRedo}
523
527
  disabled={!canRedo}
524
- title="Redo (⌘⇧Z)"
528
+ title={t('redo') + ' (⌘⇧Z)'}
525
529
  >
526
530
  <Redo className="size-4" />
527
531
  </Button>
@@ -534,7 +538,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
534
538
  size="sm"
535
539
  onClick={handleSave}
536
540
  disabled={!isDirty || saveStatus === 'saving'}
537
- title="Save (⌘S)"
541
+ title={tCommon('save') + ' (⌘S)'}
538
542
  className="text-xs gap-1"
539
543
  >
540
544
  <Save className="size-3" />
@@ -547,7 +551,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
547
551
  variant="ghost"
548
552
  size="icon-sm"
549
553
  onClick={toggleViewMode}
550
- title={viewMode === 'preview' ? 'Show source code' : 'Show preview'}
554
+ title={viewMode === 'preview' ? t('showSourceCode') : t('showPreview')}
551
555
  className={viewMode === 'preview' ? 'bg-accent' : ''}
552
556
  >
553
557
  {viewMode === 'preview' ? <Code className="size-4" /> : <Eye className="size-4" />}
@@ -560,7 +564,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
560
564
  <Button
561
565
  variant="ghost"
562
566
  size="icon-sm"
563
- title={selection ? `Add lines L${selection.startLine}-${selection.endLine} to chat` : "Add file to chat"}
567
+ title={selection ? t('addLinesToChat', { startLine: selection.startLine, endLine: selection.endLine }) : t('addFileToChat')}
564
568
  className="relative"
565
569
  >
566
570
  <AtSign className="size-4" />
@@ -615,7 +619,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
615
619
  <Button
616
620
  variant="ghost"
617
621
  size="icon-sm"
618
- title="Export"
622
+ title={t('export')}
619
623
  >
620
624
  <Download className="size-4" />
621
625
  </Button>
@@ -642,7 +646,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
642
646
  <div className="flex items-center gap-2 min-w-0 flex-1 justify-end">
643
647
  {/* Sync indicator */}
644
648
  {fileSync.isPolling && (
645
- <span title="Checking for changes...">
649
+ <span title={t('checkingForChanges')}>
646
650
  <RefreshCw className="size-3 animate-spin text-muted-foreground" />
647
651
  </span>
648
652
  )}
@@ -652,7 +656,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
652
656
  size="sm"
653
657
  onClick={() => setShowDiffResolver(true)}
654
658
  className="text-xs gap-1 text-amber-600 dark:text-amber-400 hover:text-amber-600 dark:hover:text-amber-400"
655
- title="External changes detected - click to resolve"
659
+ title={t('externalChangesDetected')}
656
660
  >
657
661
  <AlertCircle className="size-3" />
658
662
  <span className="hidden sm:inline">Conflict</span>
@@ -687,7 +691,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
687
691
  closeSearch();
688
692
  }
689
693
  }}
690
- placeholder="Search..."
694
+ placeholder={t('searchPlaceholder')}
691
695
  className="flex-1 min-w-0 bg-transparent border-0 outline-none text-sm placeholder:text-muted-foreground"
692
696
  />
693
697
  {searchQuery && (
@@ -700,7 +704,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
700
704
  size="icon-sm"
701
705
  onClick={handlePrevMatch}
702
706
  disabled={totalMatches === 0}
703
- title="Previous (⇧Enter)"
707
+ title={t('previousMatch') + ' (⇧Enter)'}
704
708
  className="shrink-0"
705
709
  >
706
710
  <span className="text-xs">↑</span>
@@ -710,7 +714,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
710
714
  size="icon-sm"
711
715
  onClick={handleNextMatch}
712
716
  disabled={totalMatches === 0}
713
- title="Next (Enter)"
717
+ title={t('nextMatch') + ' (Enter)'}
714
718
  className="shrink-0"
715
719
  >
716
720
  <span className="text-xs">↓</span>
@@ -721,7 +725,7 @@ export function FileTabContent({ tabId, filePath }: FileTabContentProps) {
721
725
  variant="ghost"
722
726
  size="icon-sm"
723
727
  onClick={closeSearch}
724
- title="Close (Esc)"
728
+ title={tCommon('close') + ' (Esc)'}
725
729
  className="shrink-0"
726
730
  >
727
731
  <X className="size-4" />