@agent-link/server 0.1.160 → 0.1.161

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.
package/web/app.js CHANGED
@@ -23,6 +23,7 @@ import { TEMPLATES, TEMPLATE_KEYS, buildFullLeadPrompt } from './modules/teamTem
23
23
  import { createLoop } from './modules/loop.js';
24
24
  import { LOOP_TEMPLATES, LOOP_TEMPLATE_KEYS, buildCronExpression, formatSchedule } from './modules/loopTemplates.js';
25
25
  import { createScrollManager, createHighlightScheduler, formatUsage } from './modules/appHelpers.js';
26
+ import { createI18n } from './modules/i18n.js';
26
27
 
27
28
  // ── App ─────────────────────────────────────────────────────────────────────
28
29
  const App = {
@@ -267,6 +268,25 @@ const App = {
267
268
  }
268
269
  applyTheme();
269
270
 
271
+ // ── i18n ──
272
+ const { t, locale, setLocale, toggleLocale, localeLabel } = createI18n();
273
+
274
+ // Map internal English status values to translated display strings
275
+ const STATUS_KEYS = {
276
+ 'No Session': 'status.noSession',
277
+ 'Connecting...': 'status.connecting',
278
+ 'Connected': 'status.connected',
279
+ 'Waiting': 'status.waiting',
280
+ 'Reconnecting...': 'status.reconnecting',
281
+ 'Disconnected': 'status.disconnected',
282
+ 'Authentication Required': 'status.authRequired',
283
+ 'Locked': 'status.locked',
284
+ };
285
+ const displayStatus = computed(() => {
286
+ const key = STATUS_KEYS[status.value];
287
+ return key ? t(key) : status.value;
288
+ });
289
+
270
290
  // ── Scroll management ──
271
291
  const { onScroll: onMessageListScroll, scrollToBottom, cleanup: cleanupScroll } = createScrollManager('.message-list');
272
292
 
@@ -296,8 +316,9 @@ const App = {
296
316
  // Multi-session parallel
297
317
  currentConversationId, conversationCache, processingConversations,
298
318
  switchConversation,
319
+ // i18n
320
+ t,
299
321
  });
300
-
301
322
  const { connect, wsSend, closeWs, submitPassword, setDequeueNext, setFileBrowser, setFilePreview, setTeam, setLoop, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap } = createConnection({
302
323
  status, agentName, hostname, workDir, sessionId, error,
303
324
  serverVersion, agentVersion, latency,
@@ -310,6 +331,8 @@ const App = {
310
331
  // Multi-session parallel
311
332
  currentConversationId, processingConversations, conversationCache,
312
333
  switchConversation,
334
+ // i18n
335
+ t,
313
336
  });
314
337
 
315
338
  // Now wire up the forwarding function
@@ -534,21 +557,23 @@ const App = {
534
557
  sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
535
558
  getRenderedContent, copyMessage, toggleTool,
536
559
  isPrevAssistant: _isPrevAssistant,
537
- toggleContextSummary, formatTimestamp, formatUsage,
538
- getToolIcon, getToolSummary, isEditTool, getEditDiffHtml, getFormattedToolInput, autoResize,
560
+ toggleContextSummary, formatTimestamp, formatUsage: (u) => formatUsage(u, t),
561
+ getToolIcon, getToolSummary: (msg) => getToolSummary(msg, t), isEditTool, getEditDiffHtml: (msg) => getEditDiffHtml(msg, t), getFormattedToolInput: (msg) => getFormattedToolInput(msg, t), autoResize,
539
562
  // AskUserQuestion
540
563
  selectQuestionOption,
541
564
  submitQuestionAnswer: _submitQuestionAnswer,
542
565
  hasQuestionAnswer, getQuestionResponseSummary,
543
566
  // Theme
544
567
  theme, toggleTheme,
568
+ // i18n
569
+ t, locale, toggleLocale, localeLabel, displayStatus,
545
570
  // Sidebar
546
571
  sidebarOpen, historySessions, currentClaudeSessionId, loadingSessions, loadingHistory,
547
572
  toggleSidebar: sidebar.toggleSidebar,
548
573
  resumeSession: sidebar.resumeSession,
549
574
  newConversation: sidebar.newConversation,
550
575
  requestSessionList: sidebar.requestSessionList,
551
- formatRelativeTime,
576
+ formatRelativeTime: (ts) => formatRelativeTime(ts, t),
552
577
  groupedSessions: sidebar.groupedSessions,
553
578
  isSessionProcessing: sidebar.isSessionProcessing,
554
579
  processingConversations,
@@ -574,9 +599,9 @@ const App = {
574
599
  // Team rename/delete
575
600
  renamingTeamId, renameTeamText,
576
601
  deleteTeamConfirmOpen, deleteTeamConfirmTitle, pendingDeleteTeamId,
577
- startTeamRename(t) {
578
- renamingTeamId.value = t.teamId;
579
- renameTeamText.value = t.title || '';
602
+ startTeamRename(tm) {
603
+ renamingTeamId.value = tm.teamId;
604
+ renameTeamText.value = tm.title || '';
580
605
  },
581
606
  confirmTeamRename() {
582
607
  const tid = renamingTeamId.value;
@@ -590,9 +615,9 @@ const App = {
590
615
  renamingTeamId.value = null;
591
616
  renameTeamText.value = '';
592
617
  },
593
- requestDeleteTeam(t) {
594
- pendingDeleteTeamId.value = t.teamId;
595
- deleteTeamConfirmTitle.value = t.title || t.teamId.slice(0, 8);
618
+ requestDeleteTeam(tm) {
619
+ pendingDeleteTeamId.value = tm.teamId;
620
+ deleteTeamConfirmTitle.value = tm.title || tm.teamId.slice(0, 8);
596
621
  deleteTeamConfirmOpen.value = true;
597
622
  },
598
623
  confirmDeleteTeam() {
@@ -938,7 +963,7 @@ const App = {
938
963
  loopLastRunDisplay(l) {
939
964
  if (!l.lastExecution) return '';
940
965
  const exec = l.lastExecution;
941
- const ago = formatRelativeTime(exec.startedAt);
966
+ const ago = formatRelativeTime(exec.startedAt, t);
942
967
  const icon = exec.status === 'success' ? 'OK' : exec.status === 'error' ? 'ERR' : exec.status;
943
968
  return ago + ' ' + icon;
944
969
  },
@@ -966,41 +991,42 @@ const App = {
966
991
  <div class="layout">
967
992
  <header class="top-bar">
968
993
  <div class="top-bar-left">
969
- <button class="sidebar-toggle" @click="toggleSidebar" title="Toggle sidebar">
994
+ <button class="sidebar-toggle" @click="toggleSidebar" :title="t('header.toggleSidebar')">
970
995
  <svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
971
996
  </button>
972
997
  <h1>AgentLink</h1>
973
998
  </div>
974
999
  <div class="top-bar-info">
975
- <span :class="['badge', status.toLowerCase()]">{{ status }}</span>
1000
+ <span :class="['badge', status.toLowerCase()]">{{ displayStatus }}</span>
976
1001
  <span v-if="latency !== null && status === 'Connected'" class="latency" :class="{ good: latency < 100, ok: latency >= 100 && latency < 500, bad: latency >= 500 }">{{ latency }}ms</span>
977
1002
  <span v-if="agentName" class="agent-label">{{ agentName }}</span>
978
1003
  <div class="team-mode-toggle">
979
- <button :class="['team-mode-btn', { active: viewMode === 'chat' }]" @click="viewMode = 'chat'">Chat</button>
980
- <button :class="['team-mode-btn', { active: viewMode === 'team' }]" @click="viewMode = 'team'">Team</button>
981
- <button :class="['team-mode-btn', { active: viewMode === 'loop' }]" @click="viewMode = 'loop'">Loop</button>
1004
+ <button :class="['team-mode-btn', { active: viewMode === 'chat' }]" @click="viewMode = 'chat'">{{ t('header.chat') }}</button>
1005
+ <button :class="['team-mode-btn', { active: viewMode === 'team' }]" @click="viewMode = 'team'">{{ t('header.team') }}</button>
1006
+ <button :class="['team-mode-btn', { active: viewMode === 'loop' }]" @click="viewMode = 'loop'">{{ t('header.loop') }}</button>
982
1007
  </div>
983
1008
  <select class="team-mode-select" :value="viewMode" @change="viewMode = $event.target.value">
984
- <option value="chat">Chat</option>
985
- <option value="team">Team</option>
986
- <option value="loop">Loop</option>
1009
+ <option value="chat">{{ t('header.chat') }}</option>
1010
+ <option value="team">{{ t('header.team') }}</option>
1011
+ <option value="loop">{{ t('header.loop') }}</option>
987
1012
  </select>
988
- <button class="theme-toggle" @click="toggleTheme" :title="theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'">
1013
+ <button class="theme-toggle" @click="toggleTheme" :title="theme === 'dark' ? t('header.lightMode') : t('header.darkMode')">
989
1014
  <svg v-if="theme === 'dark'" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41l-1.06-1.06zm1.06-10.96a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/></svg>
990
1015
  <svg v-else viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z"/></svg>
991
1016
  </button>
1017
+ <button class="theme-toggle" @click="toggleLocale" :title="localeLabel">{{ localeLabel }}</button>
992
1018
  </div>
993
1019
  </header>
994
1020
 
995
1021
  <div v-if="status === 'No Session' || (status !== 'Connected' && status !== 'Connecting...' && status !== 'Reconnecting...' && messages.length === 0)" class="center-card">
996
1022
  <div class="status-card">
997
1023
  <p class="status">
998
- <span class="label">Status:</span>
999
- <span :class="['badge', status.toLowerCase()]">{{ status }}</span>
1024
+ <span class="label">{{ t('statusCard.status') }}</span>
1025
+ <span :class="['badge', status.toLowerCase()]">{{ displayStatus }}</span>
1000
1026
  </p>
1001
- <p v-if="agentName" class="info"><span class="label">Agent:</span> {{ agentName }}</p>
1002
- <p v-if="workDir" class="info"><span class="label">Directory:</span> {{ workDir }}</p>
1003
- <p v-if="sessionId" class="info muted"><span class="label">Session:</span> {{ sessionId }}</p>
1027
+ <p v-if="agentName" class="info"><span class="label">{{ t('statusCard.agent') }}</span> {{ agentName }}</p>
1028
+ <p v-if="workDir" class="info"><span class="label">{{ t('statusCard.directory') }}</span> {{ workDir }}</p>
1029
+ <p v-if="sessionId" class="info muted"><span class="label">{{ t('statusCard.session') }}</span> {{ sessionId }}</p>
1004
1030
  <p v-if="error" class="error-msg">{{ error }}</p>
1005
1031
  </div>
1006
1032
  </div>
@@ -1015,16 +1041,16 @@ const App = {
1015
1041
  <div class="file-panel-mobile-header">
1016
1042
  <button class="file-panel-mobile-back" @click="sidebarView = 'sessions'">
1017
1043
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
1018
- Sessions
1044
+ {{ t('sidebar.sessions') }}
1019
1045
  </button>
1020
- <button class="file-panel-btn" @click="fileBrowser.refreshTree()" title="Refresh">
1046
+ <button class="file-panel-btn" @click="fileBrowser.refreshTree()" :title="t('sidebar.refresh')">
1021
1047
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
1022
1048
  </button>
1023
1049
  </div>
1024
1050
  <div class="file-panel-breadcrumb" :title="workDir">{{ workDir }}</div>
1025
- <div v-if="fileTreeLoading" class="file-panel-loading">Loading...</div>
1051
+ <div v-if="fileTreeLoading" class="file-panel-loading">{{ t('filePanel.loading') }}</div>
1026
1052
  <div v-else-if="!fileTreeRoot || !fileTreeRoot.children || fileTreeRoot.children.length === 0" class="file-panel-empty">
1027
- No files found.
1053
+ {{ t('filePanel.noFiles') }}
1028
1054
  </div>
1029
1055
  <div v-else class="file-tree">
1030
1056
  <template v-for="item in flattenedTree" :key="item.node.path">
@@ -1042,7 +1068,7 @@ const App = {
1042
1068
  <span class="file-tree-name" :title="item.node.path">{{ item.node.name }}</span>
1043
1069
  <span v-if="item.node.loading" class="file-tree-spinner"></span>
1044
1070
  </div>
1045
- <div v-if="item.node.type === 'directory' && item.node.expanded && item.node.children && item.node.children.length === 0 && !item.node.loading" class="file-tree-empty" :style="{ paddingLeft: ((item.depth + 1) * 16 + 8) + 'px' }">(empty)</div>
1071
+ <div v-if="item.node.type === 'directory' && item.node.expanded && item.node.children && item.node.children.length === 0 && !item.node.loading" class="file-tree-empty" :style="{ paddingLeft: ((item.depth + 1) * 16 + 8) + 'px' }">{{ t('filePanel.empty') }}</div>
1046
1072
  <div v-if="item.node.error" class="file-tree-error" :style="{ paddingLeft: ((item.depth + 1) * 16 + 8) + 'px' }">{{ item.node.error }}</div>
1047
1073
  </template>
1048
1074
  </div>
@@ -1053,13 +1079,13 @@ const App = {
1053
1079
  <div class="file-preview-mobile-header">
1054
1080
  <button class="file-panel-mobile-back" @click="filePreview.closePreview()">
1055
1081
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
1056
- Files
1082
+ {{ t('sidebar.files') }}
1057
1083
  </button>
1058
1084
  <div class="preview-header-actions">
1059
1085
  <button v-if="previewFile?.content && filePreview.isMarkdownFile(previewFile.fileName)"
1060
1086
  class="preview-md-toggle" :class="{ active: previewMarkdownRendered }"
1061
1087
  @click="previewMarkdownRendered = !previewMarkdownRendered"
1062
- :title="previewMarkdownRendered ? 'Show source' : 'Render markdown'">
1088
+ :title="previewMarkdownRendered ? t('preview.showSource') : t('preview.renderMarkdown')">
1063
1089
  <svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"/></svg>
1064
1090
  </button>
1065
1091
  <span v-if="previewFile" class="file-preview-mobile-size">
@@ -1068,10 +1094,10 @@ const App = {
1068
1094
  </div>
1069
1095
  </div>
1070
1096
  <div class="file-preview-mobile-filename" :title="previewFile?.filePath">
1071
- {{ previewFile?.fileName || 'Preview' }}
1097
+ {{ previewFile?.fileName || t('preview.preview') }}
1072
1098
  </div>
1073
1099
  <div class="preview-panel-body">
1074
- <div v-if="previewLoading" class="preview-loading">Loading...</div>
1100
+ <div v-if="previewLoading" class="preview-loading">{{ t('preview.loading') }}</div>
1075
1101
  <div v-else-if="previewFile?.error" class="preview-error">
1076
1102
  {{ previewFile.error }}
1077
1103
  </div>
@@ -1086,11 +1112,11 @@ const App = {
1086
1112
  <div v-else-if="previewFile?.content" class="preview-text-container">
1087
1113
  <pre class="preview-code"><code v-html="filePreview.highlightCode(previewFile.content, previewFile.fileName)"></code></pre>
1088
1114
  <div v-if="previewFile.truncated" class="preview-truncated-notice">
1089
- File truncated showing first 100 KB of {{ filePreview.formatFileSize(previewFile.totalSize) }}
1115
+ {{ t('preview.fileTruncated', { size: filePreview.formatFileSize(previewFile.totalSize) }) }}
1090
1116
  </div>
1091
1117
  </div>
1092
1118
  <div v-else-if="previewFile && !previewFile.content && !previewFile.error" class="preview-binary-info">
1093
- <p>Binary file — {{ previewFile.mimeType }}</p>
1119
+ <p>{{ t('preview.binaryFile') }} — {{ previewFile.mimeType }}</p>
1094
1120
  <p>{{ filePreview.formatFileSize(previewFile.totalSize) }}</p>
1095
1121
  </div>
1096
1122
  </div>
@@ -1105,7 +1131,7 @@ const App = {
1105
1131
  <span>{{ hostname }}</span>
1106
1132
  </div>
1107
1133
  <div class="sidebar-workdir-header">
1108
- <div class="sidebar-workdir-label">Working Directory</div>
1134
+ <div class="sidebar-workdir-label">{{ t('sidebar.workingDirectory') }}</div>
1109
1135
  </div>
1110
1136
  <div class="sidebar-workdir-path-row" @click.stop="toggleWorkdirMenu()">
1111
1137
  <div class="sidebar-workdir-path" :title="workDir">{{ workDir }}</div>
@@ -1114,19 +1140,19 @@ const App = {
1114
1140
  <div v-if="workdirMenuOpen" class="workdir-menu">
1115
1141
  <div class="workdir-menu-item" @click.stop="workdirMenuBrowse()">
1116
1142
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10zM8 13h8v2H8v-2z"/></svg>
1117
- <span>Browse files</span>
1143
+ <span>{{ t('sidebar.browseFiles') }}</span>
1118
1144
  </div>
1119
1145
  <div class="workdir-menu-item" @click.stop="workdirMenuChangeDir()">
1120
1146
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
1121
- <span>Change directory</span>
1147
+ <span>{{ t('sidebar.changeDirectory') }}</span>
1122
1148
  </div>
1123
1149
  <div class="workdir-menu-item" @click.stop="workdirMenuCopyPath()">
1124
1150
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
1125
- <span>Copy path</span>
1151
+ <span>{{ t('sidebar.copyPath') }}</span>
1126
1152
  </div>
1127
1153
  </div>
1128
1154
  <div v-if="filteredWorkdirHistory.length > 0" class="workdir-history">
1129
- <div class="workdir-history-label">Recent Directories</div>
1155
+ <div class="workdir-history-label">{{ t('sidebar.recentDirectories') }}</div>
1130
1156
  <div class="workdir-history-list">
1131
1157
  <div
1132
1158
  v-for="path in filteredWorkdirHistory" :key="path"
@@ -1135,7 +1161,7 @@ const App = {
1135
1161
  :title="path"
1136
1162
  >
1137
1163
  <span class="workdir-history-path">{{ path }}</span>
1138
- <button class="workdir-history-delete" @click.stop="removeFromWorkdirHistory(path)" title="Remove from history">
1164
+ <button class="workdir-history-delete" @click.stop="removeFromWorkdirHistory(path)" :title="t('sidebar.removeFromHistory')">
1139
1165
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
1140
1166
  </button>
1141
1167
  </div>
@@ -1147,12 +1173,12 @@ const App = {
1147
1173
  <!-- Chat History section -->
1148
1174
  <div class="sidebar-section sidebar-sessions" :style="{ flex: chatsCollapsed ? '0 0 auto' : '1 1 0', minHeight: chatsCollapsed ? 'auto' : '0' }">
1149
1175
  <div class="sidebar-section-header" @click="chatsCollapsed = !chatsCollapsed" style="cursor: pointer;">
1150
- <span>Chat History</span>
1176
+ <span>{{ t('sidebar.chatHistory') }}</span>
1151
1177
  <span class="sidebar-section-header-actions">
1152
- <button class="sidebar-refresh-btn" @click.stop="requestSessionList" title="Refresh" :disabled="loadingSessions">
1178
+ <button class="sidebar-refresh-btn" @click.stop="requestSessionList" :title="t('sidebar.refresh')" :disabled="loadingSessions">
1153
1179
  <svg :class="{ spinning: loadingSessions }" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
1154
1180
  </button>
1155
- <button class="sidebar-collapse-btn" :title="chatsCollapsed ? 'Expand' : 'Collapse'">
1181
+ <button class="sidebar-collapse-btn" :title="chatsCollapsed ? t('sidebar.expand') : t('sidebar.collapse')">
1156
1182
  <svg :class="{ collapsed: chatsCollapsed }" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>
1157
1183
  </button>
1158
1184
  </span>
@@ -1161,14 +1187,14 @@ const App = {
1161
1187
  <div v-show="!chatsCollapsed" class="sidebar-section-collapsible">
1162
1188
  <button class="new-conversation-btn" @click="newConversation">
1163
1189
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
1164
- New conversation
1190
+ {{ t('sidebar.newConversation') }}
1165
1191
  </button>
1166
1192
 
1167
1193
  <div v-if="loadingSessions && historySessions.length === 0" class="sidebar-loading">
1168
- Loading sessions...
1194
+ {{ t('sidebar.loadingSessions') }}
1169
1195
  </div>
1170
1196
  <div v-else-if="historySessions.length === 0" class="sidebar-empty">
1171
- No previous sessions found.
1197
+ {{ t('sidebar.noSessions') }}
1172
1198
  </div>
1173
1199
  <div v-else class="session-list">
1174
1200
  <div v-for="group in groupedSessions" :key="group.label" class="session-group">
@@ -1189,8 +1215,8 @@ const App = {
1189
1215
  @keydown.escape.stop="cancelRename"
1190
1216
  @vue:mounted="$event.el.focus()"
1191
1217
  />
1192
- <button class="session-rename-ok" @click.stop="confirmRename" title="Confirm">&#10003;</button>
1193
- <button class="session-rename-cancel" @click.stop="cancelRename" title="Cancel">&times;</button>
1218
+ <button class="session-rename-ok" @click.stop="confirmRename" :title="t('sidebar.confirm')">&#10003;</button>
1219
+ <button class="session-rename-cancel" @click.stop="cancelRename" :title="t('sidebar.cancel')">&times;</button>
1194
1220
  </div>
1195
1221
  <div v-else class="session-title">
1196
1222
  <svg v-if="s.title && s.title.startsWith('You are a team lead')" class="session-team-icon" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
@@ -1202,7 +1228,7 @@ const App = {
1202
1228
  <button
1203
1229
  class="session-rename-btn"
1204
1230
  @click.stop="startRename(s)"
1205
- title="Rename session"
1231
+ :title="t('sidebar.renameSession')"
1206
1232
  >
1207
1233
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
1208
1234
  </button>
@@ -1210,7 +1236,7 @@ const App = {
1210
1236
  v-if="currentClaudeSessionId !== s.sessionId"
1211
1237
  class="session-delete-btn"
1212
1238
  @click.stop="deleteSession(s)"
1213
- title="Delete session"
1239
+ :title="t('sidebar.deleteSession')"
1214
1240
  >
1215
1241
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
1216
1242
  </button>
@@ -1225,12 +1251,12 @@ const App = {
1225
1251
  <!-- Teams section -->
1226
1252
  <div class="sidebar-section sidebar-teams" :style="{ flex: teamsCollapsed ? '0 0 auto' : '1 1 0', minHeight: teamsCollapsed ? 'auto' : '0' }">
1227
1253
  <div class="sidebar-section-header" @click="teamsCollapsed = !teamsCollapsed" style="cursor: pointer;">
1228
- <span>Teams History</span>
1254
+ <span>{{ t('sidebar.teamsHistory') }}</span>
1229
1255
  <span class="sidebar-section-header-actions">
1230
- <button class="sidebar-refresh-btn" @click.stop="requestTeamsList" title="Refresh" :disabled="loadingTeams">
1256
+ <button class="sidebar-refresh-btn" @click.stop="requestTeamsList" :title="t('sidebar.refresh')" :disabled="loadingTeams">
1231
1257
  <svg :class="{ spinning: loadingTeams }" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
1232
1258
  </button>
1233
- <button class="sidebar-collapse-btn" :title="teamsCollapsed ? 'Expand' : 'Collapse'">
1259
+ <button class="sidebar-collapse-btn" :title="teamsCollapsed ? t('sidebar.expand') : t('sidebar.collapse')">
1234
1260
  <svg :class="{ collapsed: teamsCollapsed }" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>
1235
1261
  </button>
1236
1262
  </span>
@@ -1239,18 +1265,18 @@ const App = {
1239
1265
  <div v-show="!teamsCollapsed" class="sidebar-section-collapsible">
1240
1266
  <button class="new-conversation-btn" @click="newTeam" :disabled="isTeamActive">
1241
1267
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
1242
- New team
1268
+ {{ t('sidebar.newTeam') }}
1243
1269
  </button>
1244
1270
 
1245
1271
  <div class="team-history-list">
1246
1272
  <div
1247
- v-for="t in teamsList" :key="t.teamId"
1248
- :class="['team-history-item', { active: displayTeam && displayTeam.teamId === t.teamId }]"
1249
- @click="renamingTeamId !== t.teamId && viewHistoricalTeam(t.teamId)"
1250
- :title="t.title"
1273
+ v-for="tm in teamsList" :key="tm.teamId"
1274
+ :class="['team-history-item', { active: displayTeam && displayTeam.teamId === tm.teamId }]"
1275
+ @click="renamingTeamId !== tm.teamId && viewHistoricalTeam(tm.teamId)"
1276
+ :title="tm.title"
1251
1277
  >
1252
1278
  <div class="team-history-info">
1253
- <div v-if="renamingTeamId === t.teamId" class="session-rename-row">
1279
+ <div v-if="renamingTeamId === tm.teamId" class="session-rename-row">
1254
1280
  <input
1255
1281
  class="session-rename-input"
1256
1282
  v-model="renameTeamText"
@@ -1259,19 +1285,19 @@ const App = {
1259
1285
  @keydown.escape.stop="cancelTeamRename"
1260
1286
  @vue:mounted="$event.el.focus()"
1261
1287
  />
1262
- <button class="session-rename-ok" @click.stop="confirmTeamRename" title="Confirm">&#10003;</button>
1263
- <button class="session-rename-cancel" @click.stop="cancelTeamRename" title="Cancel">&times;</button>
1288
+ <button class="session-rename-ok" @click.stop="confirmTeamRename" :title="t('sidebar.confirm')">&#10003;</button>
1289
+ <button class="session-rename-cancel" @click.stop="cancelTeamRename" :title="t('sidebar.cancel')">&times;</button>
1264
1290
  </div>
1265
- <div v-else class="team-history-title">{{ t.title || 'Untitled team' }}</div>
1266
- <div v-if="renamingTeamId !== t.teamId" class="team-history-meta">
1267
- <span :class="['team-status-badge', 'team-status-badge-sm', 'team-status-' + t.status]">{{ t.status }}</span>
1268
- <span v-if="t.taskCount" class="team-history-tasks">{{ t.taskCount }} tasks</span>
1269
- <span v-if="t.totalCost" class="team-history-tasks">{{'$' + t.totalCost.toFixed(2) }}</span>
1291
+ <div v-else class="team-history-title">{{ tm.title || t('sidebar.untitledTeam') }}</div>
1292
+ <div v-if="renamingTeamId !== tm.teamId" class="team-history-meta">
1293
+ <span :class="['team-status-badge', 'team-status-badge-sm', 'team-status-' + tm.status]">{{ tm.status }}</span>
1294
+ <span v-if="tm.taskCount" class="team-history-tasks">{{ tm.taskCount }} {{ t('sidebar.tasks') }}</span>
1295
+ <span v-if="tm.totalCost" class="team-history-tasks">{{'$' + tm.totalCost.toFixed(2) }}</span>
1270
1296
  <span class="session-actions">
1271
- <button class="session-rename-btn" @click.stop="startTeamRename(t)" title="Rename team">
1297
+ <button class="session-rename-btn" @click.stop="startTeamRename(tm)" :title="t('sidebar.renameTeam')">
1272
1298
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
1273
1299
  </button>
1274
- <button class="session-delete-btn" @click.stop="requestDeleteTeam(t)" title="Delete team">
1300
+ <button class="session-delete-btn" @click.stop="requestDeleteTeam(tm)" :title="t('sidebar.deleteTeam')">
1275
1301
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
1276
1302
  </button>
1277
1303
  </span>
@@ -1285,12 +1311,12 @@ const App = {
1285
1311
  <!-- Loops section -->
1286
1312
  <div class="sidebar-section sidebar-loops" :style="{ flex: loopsCollapsed ? '0 0 auto' : '1 1 0', minHeight: loopsCollapsed ? 'auto' : '0' }">
1287
1313
  <div class="sidebar-section-header" @click="loopsCollapsed = !loopsCollapsed" style="cursor: pointer;">
1288
- <span>Loops</span>
1314
+ <span>{{ t('sidebar.loops') }}</span>
1289
1315
  <span class="sidebar-section-header-actions">
1290
- <button class="sidebar-refresh-btn" @click.stop="requestLoopsList" title="Refresh" :disabled="loadingLoops">
1316
+ <button class="sidebar-refresh-btn" @click.stop="requestLoopsList" :title="t('sidebar.refresh')" :disabled="loadingLoops">
1291
1317
  <svg :class="{ spinning: loadingLoops }" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
1292
1318
  </button>
1293
- <button class="sidebar-collapse-btn" :title="loopsCollapsed ? 'Expand' : 'Collapse'">
1319
+ <button class="sidebar-collapse-btn" :title="loopsCollapsed ? t('sidebar.expand') : t('sidebar.collapse')">
1294
1320
  <svg :class="{ collapsed: loopsCollapsed }" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>
1295
1321
  </button>
1296
1322
  </span>
@@ -1299,11 +1325,11 @@ const App = {
1299
1325
  <div v-show="!loopsCollapsed" class="sidebar-section-collapsible">
1300
1326
  <button class="new-conversation-btn" @click="newLoop">
1301
1327
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
1302
- New loop
1328
+ {{ t('sidebar.newLoop') }}
1303
1329
  </button>
1304
1330
 
1305
1331
  <div v-if="loopsList.length === 0 && !loadingLoops" class="sidebar-empty">
1306
- No loops configured.
1332
+ {{ t('sidebar.noLoops') }}
1307
1333
  </div>
1308
1334
  <div v-else class="loop-history-list">
1309
1335
  <div
@@ -1322,18 +1348,18 @@ const App = {
1322
1348
  @keydown.escape.stop="cancelLoopRename"
1323
1349
  @vue:mounted="$event.el.focus()"
1324
1350
  />
1325
- <button class="session-rename-ok" @click.stop="confirmLoopRename" title="Confirm">&#10003;</button>
1326
- <button class="session-rename-cancel" @click.stop="cancelLoopRename" title="Cancel">&times;</button>
1351
+ <button class="session-rename-ok" @click.stop="confirmLoopRename" :title="t('sidebar.confirm')">&#10003;</button>
1352
+ <button class="session-rename-cancel" @click.stop="cancelLoopRename" :title="t('sidebar.cancel')">&times;</button>
1327
1353
  </div>
1328
- <div v-else class="team-history-title">{{ l.name || 'Untitled loop' }}</div>
1354
+ <div v-else class="team-history-title">{{ l.name || t('sidebar.untitledLoop') }}</div>
1329
1355
  <div v-if="renamingLoopId !== l.id" class="team-history-meta">
1330
- <span :class="['team-status-badge', 'team-status-badge-sm', l.enabled ? 'team-status-running' : 'team-status-completed']">{{ l.enabled ? 'active' : 'paused' }}</span>
1356
+ <span :class="['team-status-badge', 'team-status-badge-sm', l.enabled ? 'team-status-running' : 'team-status-completed']">{{ l.enabled ? t('sidebar.active') : t('sidebar.paused') }}</span>
1331
1357
  <span v-if="l.scheduleType" class="team-history-tasks">{{ formatSchedule(l.scheduleType, l.scheduleConfig || {}, l.schedule) }}</span>
1332
1358
  <span class="session-actions">
1333
- <button class="session-rename-btn" @click.stop="startLoopRename(l)" title="Rename loop">
1359
+ <button class="session-rename-btn" @click.stop="startLoopRename(l)" :title="t('sidebar.renameLoop')">
1334
1360
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
1335
1361
  </button>
1336
- <button class="session-delete-btn" @click.stop="requestDeleteLoop(l)" title="Delete loop">
1362
+ <button class="session-delete-btn" @click.stop="requestDeleteLoop(l)" :title="t('sidebar.deleteLoop')">
1337
1363
  <svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
1338
1364
  </button>
1339
1365
  </span>
@@ -1345,9 +1371,9 @@ const App = {
1345
1371
  </div>
1346
1372
 
1347
1373
  <div v-if="serverVersion || agentVersion" class="sidebar-version-footer">
1348
- <span v-if="serverVersion">server {{ serverVersion }}</span>
1374
+ <span v-if="serverVersion">{{ t('sidebar.server') }} {{ serverVersion }}</span>
1349
1375
  <span v-if="serverVersion && agentVersion" class="sidebar-version-sep">/</span>
1350
- <span v-if="agentVersion">agent {{ agentVersion }}</span>
1376
+ <span v-if="agentVersion">{{ t('sidebar.agent') }} {{ agentVersion }}</span>
1351
1377
  </div>
1352
1378
  </template>
1353
1379
  </aside>
@@ -1357,20 +1383,20 @@ const App = {
1357
1383
  <div v-if="filePanelOpen && !isMobile" class="file-panel" :style="{ width: filePanelWidth + 'px' }">
1358
1384
  <div class="file-panel-resize-handle" @mousedown="fileBrowser.onResizeStart($event)" @touchstart="fileBrowser.onResizeStart($event)"></div>
1359
1385
  <div class="file-panel-header">
1360
- <span class="file-panel-title">Files</span>
1386
+ <span class="file-panel-title">{{ t('filePanel.files') }}</span>
1361
1387
  <div class="file-panel-actions">
1362
- <button class="file-panel-btn" @click="fileBrowser.refreshTree()" title="Refresh">
1388
+ <button class="file-panel-btn" @click="fileBrowser.refreshTree()" :title="t('sidebar.refresh')">
1363
1389
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
1364
1390
  </button>
1365
- <button class="file-panel-btn" @click="filePanelOpen = false" title="Close">
1391
+ <button class="file-panel-btn" @click="filePanelOpen = false" :title="t('sidebar.close')">
1366
1392
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
1367
1393
  </button>
1368
1394
  </div>
1369
1395
  </div>
1370
1396
  <div class="file-panel-breadcrumb" :title="workDir">{{ workDir }}</div>
1371
- <div v-if="fileTreeLoading" class="file-panel-loading">Loading...</div>
1397
+ <div v-if="fileTreeLoading" class="file-panel-loading">{{ t('filePanel.loading') }}</div>
1372
1398
  <div v-else-if="!fileTreeRoot || !fileTreeRoot.children || fileTreeRoot.children.length === 0" class="file-panel-empty">
1373
- No files found.
1399
+ {{ t('filePanel.noFiles') }}
1374
1400
  </div>
1375
1401
  <div v-else class="file-tree">
1376
1402
  <template v-for="item in flattenedTree" :key="item.node.path">
@@ -1388,7 +1414,7 @@ const App = {
1388
1414
  <span class="file-tree-name" :title="item.node.path">{{ item.node.name }}</span>
1389
1415
  <span v-if="item.node.loading" class="file-tree-spinner"></span>
1390
1416
  </div>
1391
- <div v-if="item.node.type === 'directory' && item.node.expanded && item.node.children && item.node.children.length === 0 && !item.node.loading" class="file-tree-empty" :style="{ paddingLeft: ((item.depth + 1) * 16 + 8) + 'px' }">(empty)</div>
1417
+ <div v-if="item.node.type === 'directory' && item.node.expanded && item.node.children && item.node.children.length === 0 && !item.node.loading" class="file-tree-empty" :style="{ paddingLeft: ((item.depth + 1) * 16 + 8) + 'px' }">{{ t('filePanel.empty') }}</div>
1392
1418
  <div v-if="item.node.error" class="file-tree-error" :style="{ paddingLeft: ((item.depth + 1) * 16 + 8) + 'px' }">{{ item.node.error }}</div>
1393
1419
  </template>
1394
1420
  </div>
@@ -1406,13 +1432,13 @@ const App = {
1406
1432
  <div class="team-create-inner">
1407
1433
  <div class="team-create-header">
1408
1434
  <svg viewBox="0 0 24 24" width="28" height="28"><path fill="currentColor" opacity="0.5" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
1409
- <h2>Launch Agent Team</h2>
1435
+ <h2>{{ t('team.launchAgentTeam') }}</h2>
1410
1436
  </div>
1411
- <p class="team-create-desc">Select a template, review the lead prompt, and describe your task.</p>
1437
+ <p class="team-create-desc">{{ t('team.selectTemplateDesc') }}</p>
1412
1438
 
1413
1439
  <!-- Template selector -->
1414
1440
  <div class="team-tpl-section">
1415
- <label class="team-tpl-label">Template</label>
1441
+ <label class="team-tpl-label">{{ t('team.template') }}</label>
1416
1442
  <select class="team-tpl-select" :value="selectedTemplate" @change="onTemplateChange($event.target.value)">
1417
1443
  <option v-for="key in TEMPLATE_KEYS" :key="key" :value="key">{{ TEMPLATES[key].label }}</option>
1418
1444
  </select>
@@ -1423,7 +1449,7 @@ const App = {
1423
1449
  <div class="team-lead-prompt-section">
1424
1450
  <div class="team-lead-prompt-header" @click="leadPromptExpanded = !leadPromptExpanded">
1425
1451
  <svg class="team-lead-prompt-arrow" :class="{ expanded: leadPromptExpanded }" viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6z"/></svg>
1426
- <span class="team-lead-prompt-title">Lead Prompt</span>
1452
+ <span class="team-lead-prompt-title">{{ t('team.leadPrompt') }}</span>
1427
1453
  <span v-if="!leadPromptExpanded" class="team-lead-prompt-preview">{{ leadPromptPreview() }}</span>
1428
1454
  </div>
1429
1455
  <div v-if="leadPromptExpanded" class="team-lead-prompt-body">
@@ -1433,18 +1459,18 @@ const App = {
1433
1459
  rows="10"
1434
1460
  ></textarea>
1435
1461
  <div class="team-lead-prompt-actions">
1436
- <button class="team-lead-prompt-reset" @click="resetLeadPrompt()" title="Reset to template default">Reset</button>
1462
+ <button class="team-lead-prompt-reset" @click="resetLeadPrompt()" :title="t('team.reset')">{{ t('team.reset') }}</button>
1437
1463
  </div>
1438
1464
  </div>
1439
1465
  </div>
1440
1466
 
1441
1467
  <!-- Task description -->
1442
1468
  <div class="team-tpl-section">
1443
- <label class="team-tpl-label">Task Description</label>
1469
+ <label class="team-tpl-label">{{ t('team.taskDescription') }}</label>
1444
1470
  <textarea
1445
1471
  v-model="teamInstruction"
1446
1472
  class="team-create-textarea"
1447
- placeholder="Describe the task for the team... e.g. 'Review the authentication module for security issues and fix any vulnerabilities found.'"
1473
+ :placeholder="t('team.taskPlaceholder')"
1448
1474
  rows="4"
1449
1475
  ></textarea>
1450
1476
  </div>
@@ -1452,14 +1478,14 @@ const App = {
1452
1478
  <div class="team-create-actions">
1453
1479
  <button class="team-create-launch" :disabled="!teamInstruction.trim()" @click="launchTeamFromPanel()">
1454
1480
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
1455
- Launch Team
1481
+ {{ t('team.launchTeam') }}
1456
1482
  </button>
1457
- <button class="team-create-cancel" @click="backToChat()">Back to Chat</button>
1483
+ <button class="team-create-cancel" @click="backToChat()">{{ t('team.backToChat') }}</button>
1458
1484
  </div>
1459
1485
 
1460
1486
  <!-- Example instructions -->
1461
1487
  <div class="team-examples-section">
1462
- <div class="team-examples-header">Examples</div>
1488
+ <div class="team-examples-header">{{ t('team.examples') }}</div>
1463
1489
  <div class="team-examples-list">
1464
1490
  <div class="team-example-card" v-for="(ex, i) in teamExamples" :key="i">
1465
1491
  <div class="team-example-icon" v-html="ex.icon"></div>
@@ -1467,27 +1493,27 @@ const App = {
1467
1493
  <div class="team-example-title">{{ ex.title }}</div>
1468
1494
  <div class="team-example-text">{{ ex.text }}</div>
1469
1495
  </div>
1470
- <button class="team-example-try" @click="onTemplateChange(ex.template); teamInstruction = ex.text">Try it</button>
1496
+ <button class="team-example-try" @click="onTemplateChange(ex.template); teamInstruction = ex.text">{{ t('team.tryIt') }}</button>
1471
1497
  </div>
1472
1498
  </div>
1473
1499
  </div>
1474
1500
 
1475
1501
  <!-- Historical teams -->
1476
1502
  <div v-if="teamsList.length > 0" class="team-history-section">
1477
- <div class="team-history-section-header">Previous Teams</div>
1503
+ <div class="team-history-section-header">{{ t('team.previousTeams') }}</div>
1478
1504
  <div class="team-history-list">
1479
1505
  <div
1480
- v-for="t in teamsList" :key="t.teamId"
1506
+ v-for="tm in teamsList" :key="tm.teamId"
1481
1507
  class="team-history-item"
1482
- @click="viewHistoricalTeam(t.teamId)"
1483
- :title="t.title"
1508
+ @click="viewHistoricalTeam(tm.teamId)"
1509
+ :title="tm.title"
1484
1510
  >
1485
1511
  <div class="team-history-info">
1486
- <div class="team-history-title">{{ t.title || 'Untitled team' }}</div>
1512
+ <div class="team-history-title">{{ tm.title || t('sidebar.untitledTeam') }}</div>
1487
1513
  <div class="team-history-meta">
1488
- <span :class="['team-status-badge', 'team-status-badge-sm', 'team-status-' + t.status]">{{ t.status }}</span>
1489
- <span v-if="t.taskCount" class="team-history-tasks">{{ t.taskCount }} tasks</span>
1490
- <span v-if="t.totalCost" class="team-history-tasks">{{'$' + t.totalCost.toFixed(2) }}</span>
1514
+ <span :class="['team-status-badge', 'team-status-badge-sm', 'team-status-' + tm.status]">{{ tm.status }}</span>
1515
+ <span v-if="tm.taskCount" class="team-history-tasks">{{ tm.taskCount }} {{ t('sidebar.tasks') }}</span>
1516
+ <span v-if="tm.totalCost" class="team-history-tasks">{{'$' + tm.totalCost.toFixed(2) }}</span>
1491
1517
  </div>
1492
1518
  </div>
1493
1519
  </div>
@@ -1503,15 +1529,15 @@ const App = {
1503
1529
  <div class="team-dash-header-top">
1504
1530
  <span :class="['team-status-badge', 'team-status-' + displayTeam.status]">{{ displayTeam.status }}</span>
1505
1531
  <div class="team-dash-header-right">
1506
- <button v-if="isTeamRunning" class="team-dissolve-btn" @click="dissolveTeam()">Dissolve Team</button>
1507
- <button v-if="!isTeamActive" class="team-new-btn" @click="newTeam()">New Team</button>
1508
- <button v-if="!isTeamActive" class="team-back-btn" @click="backToChat()">Back to Chat</button>
1532
+ <button v-if="isTeamRunning" class="team-dissolve-btn" @click="dissolveTeam()">{{ t('team.dissolveTeam') }}</button>
1533
+ <button v-if="!isTeamActive" class="team-new-btn" @click="newTeam()">{{ t('team.newTeam') }}</button>
1534
+ <button v-if="!isTeamActive" class="team-back-btn" @click="backToChat()">{{ t('team.backToChat') }}</button>
1509
1535
  </div>
1510
1536
  </div>
1511
1537
  <div class="team-dash-instruction" :class="{ expanded: instructionExpanded }">
1512
- <div class="team-dash-instruction-text">{{ displayTeam.config?.instruction || displayTeam.title || 'Agent Team' }}</div>
1538
+ <div class="team-dash-instruction-text">{{ displayTeam.config?.instruction || displayTeam.title || t('team.agentTeam') }}</div>
1513
1539
  <button v-if="(displayTeam.config?.instruction || '').length > 120" class="team-dash-instruction-toggle" @click="instructionExpanded = !instructionExpanded">
1514
- {{ instructionExpanded ? 'Show less' : 'Show more' }}
1540
+ {{ instructionExpanded ? t('team.showLess') : t('team.showMore') }}
1515
1541
  </button>
1516
1542
  </div>
1517
1543
  </div>
@@ -1519,7 +1545,7 @@ const App = {
1519
1545
  <!-- Lead status bar (clickable to view lead detail) -->
1520
1546
  <div v-if="displayTeam.leadStatus && (displayTeam.status === 'planning' || displayTeam.status === 'running' || displayTeam.status === 'summarizing')" class="team-lead-bar team-lead-bar-clickable" @click="viewAgent('lead')">
1521
1547
  <span class="team-lead-dot"></span>
1522
- <span class="team-lead-label">Lead</span>
1548
+ <span class="team-lead-label">{{ t('team.lead') }}</span>
1523
1549
  <span class="team-lead-text">{{ displayTeam.leadStatus }}</span>
1524
1550
  </div>
1525
1551
 
@@ -1533,14 +1559,14 @@ const App = {
1533
1559
  <div class="team-kanban-section">
1534
1560
  <div class="team-kanban-section-header" @click="kanbanExpanded = !kanbanExpanded">
1535
1561
  <span class="team-kanban-section-toggle">{{ kanbanExpanded ? '\u25BC' : '\u25B6' }}</span>
1536
- <span class="team-kanban-section-title">Tasks</span>
1537
- <span class="team-kanban-section-summary">{{ doneTasks.length }}/{{ displayTeam.tasks.length }} done</span>
1562
+ <span class="team-kanban-section-title">{{ t('team.tasks') }}</span>
1563
+ <span class="team-kanban-section-summary">{{ doneTasks.length }}/{{ displayTeam.tasks.length }} {{ t('team.done') }}</span>
1538
1564
  </div>
1539
1565
  <div v-show="kanbanExpanded" class="team-kanban">
1540
1566
  <div class="team-kanban-col">
1541
1567
  <div class="team-kanban-col-header">
1542
1568
  <span class="team-kanban-col-dot pending"></span>
1543
- Pending
1569
+ {{ t('team.pending') }}
1544
1570
  <span class="team-kanban-col-count">{{ pendingTasks.length }}</span>
1545
1571
  </div>
1546
1572
  <div class="team-kanban-col-body">
@@ -1548,13 +1574,13 @@ const App = {
1548
1574
  <div class="team-task-title">{{ task.title }}</div>
1549
1575
  <div v-if="task.description" class="team-task-desc team-task-desc-clamp" @click.stop="$event.target.classList.toggle('team-task-desc-expanded')">{{ task.description }}</div>
1550
1576
  </div>
1551
- <div v-if="pendingTasks.length === 0" class="team-kanban-empty">No tasks</div>
1577
+ <div v-if="pendingTasks.length === 0" class="team-kanban-empty">{{ t('team.noTasks') }}</div>
1552
1578
  </div>
1553
1579
  </div>
1554
1580
  <div class="team-kanban-col">
1555
1581
  <div class="team-kanban-col-header">
1556
1582
  <span class="team-kanban-col-dot active"></span>
1557
- Active
1583
+ {{ t('team.activeCol') }}
1558
1584
  <span class="team-kanban-col-count">{{ activeTasks.length }}</span>
1559
1585
  </div>
1560
1586
  <div class="team-kanban-col-body">
@@ -1566,13 +1592,13 @@ const App = {
1566
1592
  {{ getTaskAgent(task).name || task.assignee || task.assignedTo }}
1567
1593
  </div>
1568
1594
  </div>
1569
- <div v-if="activeTasks.length === 0" class="team-kanban-empty">No tasks</div>
1595
+ <div v-if="activeTasks.length === 0" class="team-kanban-empty">{{ t('team.noTasks') }}</div>
1570
1596
  </div>
1571
1597
  </div>
1572
1598
  <div class="team-kanban-col">
1573
1599
  <div class="team-kanban-col-header">
1574
1600
  <span class="team-kanban-col-dot done"></span>
1575
- Done
1601
+ {{ t('team.doneCol') }}
1576
1602
  <span class="team-kanban-col-count">{{ doneTasks.length }}</span>
1577
1603
  </div>
1578
1604
  <div class="team-kanban-col-body">
@@ -1584,13 +1610,13 @@ const App = {
1584
1610
  {{ getTaskAgent(task).name || task.assignee || task.assignedTo }}
1585
1611
  </div>
1586
1612
  </div>
1587
- <div v-if="doneTasks.length === 0" class="team-kanban-empty">No tasks</div>
1613
+ <div v-if="doneTasks.length === 0" class="team-kanban-empty">{{ t('team.noTasks') }}</div>
1588
1614
  </div>
1589
1615
  </div>
1590
1616
  <div v-if="failedTasks.length > 0" class="team-kanban-col">
1591
1617
  <div class="team-kanban-col-header">
1592
1618
  <span class="team-kanban-col-dot failed"></span>
1593
- Failed
1619
+ {{ t('team.failed') }}
1594
1620
  <span class="team-kanban-col-count">{{ failedTasks.length }}</span>
1595
1621
  </div>
1596
1622
  <div class="team-kanban-col-body">
@@ -1605,7 +1631,7 @@ const App = {
1605
1631
 
1606
1632
  <!-- Agent cards (horizontal) -->
1607
1633
  <div class="team-agents-bar">
1608
- <div class="team-agents-bar-header">Agents</div>
1634
+ <div class="team-agents-bar-header">{{ t('team.agents') }}</div>
1609
1635
  <div class="team-agents-bar-list">
1610
1636
  <div
1611
1637
  v-for="agent in (displayTeam.agents || [])" :key="agent.id"
@@ -1620,15 +1646,15 @@ const App = {
1620
1646
  <div v-if="getLatestAgentActivity(agent.id)" class="team-agent-card-activity">{{ getLatestAgentActivity(agent.id) }}</div>
1621
1647
  </div>
1622
1648
  <div v-if="!displayTeam.agents || displayTeam.agents.length === 0" class="team-agents-empty">
1623
- <span v-if="displayTeam.status === 'planning'">Planning tasks...</span>
1624
- <span v-else>No agents</span>
1649
+ <span v-if="displayTeam.status === 'planning'">{{ t('team.planningTasks') }}</span>
1650
+ <span v-else>{{ t('team.noAgents') }}</span>
1625
1651
  </div>
1626
1652
  </div>
1627
1653
  </div>
1628
1654
 
1629
1655
  <!-- Activity feed -->
1630
1656
  <div v-if="displayTeam.feed && displayTeam.feed.length > 0" class="team-feed">
1631
- <div class="team-feed-header">Activity</div>
1657
+ <div class="team-feed-header">{{ t('team.activity') }}</div>
1632
1658
  <div class="team-feed-list">
1633
1659
  <div v-for="(entry, fi) in displayTeam.feed" :key="fi" class="team-feed-entry">
1634
1660
  <span v-if="entry.agentId" class="team-agent-dot" :style="{ background: getAgentColor(entry.agentId) }"></span>
@@ -1642,26 +1668,26 @@ const App = {
1642
1668
  <!-- Completion stats -->
1643
1669
  <div v-if="displayTeam.status === 'completed' || displayTeam.status === 'failed'" class="team-stats-bar">
1644
1670
  <div class="team-stat">
1645
- <span class="team-stat-label">Tasks</span>
1671
+ <span class="team-stat-label">{{ t('team.tasksStat') }}</span>
1646
1672
  <span class="team-stat-value">{{ doneTasks.length }}/{{ displayTeam.tasks.length }}</span>
1647
1673
  </div>
1648
1674
  <div v-if="displayTeam.durationMs" class="team-stat">
1649
- <span class="team-stat-label">Duration</span>
1675
+ <span class="team-stat-label">{{ t('team.duration') }}</span>
1650
1676
  <span class="team-stat-value">{{ Math.round(displayTeam.durationMs / 1000) }}s</span>
1651
1677
  </div>
1652
1678
  <div v-if="displayTeam.totalCost" class="team-stat">
1653
- <span class="team-stat-label">Cost</span>
1679
+ <span class="team-stat-label">{{ t('team.cost') }}</span>
1654
1680
  <span class="team-stat-value">{{ '$' + displayTeam.totalCost.toFixed(2) }}</span>
1655
1681
  </div>
1656
1682
  <div class="team-stat">
1657
- <span class="team-stat-label">Agents</span>
1683
+ <span class="team-stat-label">{{ t('team.agentsStat') }}</span>
1658
1684
  <span class="team-stat-value">{{ (displayTeam.agents || []).length }}</span>
1659
1685
  </div>
1660
1686
  </div>
1661
1687
 
1662
1688
  <!-- Completion summary -->
1663
1689
  <div v-if="displayTeam.status === 'completed' && displayTeam.summary" class="team-summary">
1664
- <div class="team-summary-header">Summary</div>
1690
+ <div class="team-summary-header">{{ t('team.summary') }}</div>
1665
1691
  <div class="team-summary-body markdown-body" v-html="getRenderedContent({ role: 'assistant', content: displayTeam.summary })"></div>
1666
1692
  </div>
1667
1693
 
@@ -1670,16 +1696,16 @@ const App = {
1670
1696
  <textarea
1671
1697
  v-model="teamInstruction"
1672
1698
  class="team-new-launcher-input"
1673
- placeholder="Launch another team task..."
1699
+ :placeholder="t('team.launchAnotherPlaceholder')"
1674
1700
  rows="2"
1675
1701
  @keydown.enter.ctrl="launchTeamFromPanel()"
1676
1702
  ></textarea>
1677
1703
  <div class="team-new-launcher-actions">
1678
1704
  <button class="team-create-launch" :disabled="!teamInstruction.trim()" @click="launchTeamFromPanel()">
1679
1705
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
1680
- New Team
1706
+ {{ t('team.newTeam') }}
1681
1707
  </button>
1682
- <button class="team-create-cancel" @click="backToChat()">Back to Chat</button>
1708
+ <button class="team-create-cancel" @click="backToChat()">{{ t('team.backToChat') }}</button>
1683
1709
  </div>
1684
1710
  </div>
1685
1711
  </div>
@@ -1689,7 +1715,7 @@ const App = {
1689
1715
  <div class="team-agent-detail-header" :style="{ borderBottom: '2px solid ' + getAgentColor(activeAgentView) }">
1690
1716
  <button class="team-agent-back-btn" @click="viewDashboard()">
1691
1717
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
1692
- Dashboard
1718
+ {{ t('team.dashboard') }}
1693
1719
  </button>
1694
1720
  <span :class="['team-agent-dot', { working: findAgent(activeAgentView)?.status === 'working' || findAgent(activeAgentView)?.status === 'starting' }]" :style="{ background: getAgentColor(activeAgentView) }"></span>
1695
1721
  <span class="team-agent-detail-name" :style="{ color: getAgentColor(activeAgentView) }">{{ findAgent(activeAgentView)?.name || activeAgentView }}</span>
@@ -1698,12 +1724,12 @@ const App = {
1698
1724
  <div class="team-agent-messages">
1699
1725
  <div class="team-agent-messages-inner">
1700
1726
  <div v-if="getAgentMessages(activeAgentView).length === 0" class="team-agent-empty-msg">
1701
- No messages yet.
1727
+ {{ t('team.noMessages') }}
1702
1728
  </div>
1703
1729
  <template v-for="(msg, mi) in getAgentMessages(activeAgentView)" :key="msg.id">
1704
1730
  <!-- Agent user/prompt message -->
1705
1731
  <div v-if="msg.role === 'user' && msg.content" class="team-agent-prompt">
1706
- <div class="team-agent-prompt-label">Task Prompt</div>
1732
+ <div class="team-agent-prompt-label">{{ t('team.taskPrompt') }}</div>
1707
1733
  <div class="team-agent-prompt-body markdown-body" v-html="getRenderedContent(msg)"></div>
1708
1734
  </div>
1709
1735
  <!-- System notice (e.g. completion message) -->
@@ -1760,14 +1786,14 @@ const App = {
1760
1786
 
1761
1787
  <div v-if="loadingExecution" class="loop-loading">
1762
1788
  <div class="history-loading-spinner"></div>
1763
- <span>Loading execution...</span>
1789
+ <span>{{ t('loop.loadingExecution') }}</span>
1764
1790
  </div>
1765
1791
 
1766
1792
  <div v-else class="loop-exec-messages">
1767
- <div v-if="executionMessages.length === 0" class="team-agent-empty-msg">No messages recorded for this execution.</div>
1793
+ <div v-if="executionMessages.length === 0" class="team-agent-empty-msg">{{ t('loop.noExecMessages') }}</div>
1768
1794
  <template v-for="(msg, mi) in executionMessages" :key="msg.id">
1769
1795
  <div v-if="msg.role === 'user' && msg.content" class="team-agent-prompt">
1770
- <div class="team-agent-prompt-label">Loop Prompt</div>
1796
+ <div class="team-agent-prompt-label">{{ t('loop.loopPrompt') }}</div>
1771
1797
  <div class="team-agent-prompt-body markdown-body" v-html="getRenderedContent(msg)"></div>
1772
1798
  </div>
1773
1799
  <div v-else-if="msg.role === 'assistant'" :class="['message', 'message-assistant']">
@@ -1804,34 +1830,34 @@ const App = {
1804
1830
  <div class="loop-detail-header">
1805
1831
  <button class="team-agent-back-btn" @click="backToLoopsList()">
1806
1832
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
1807
- Back to Loops
1833
+ {{ t('loop.backToLoops') }}
1808
1834
  </button>
1809
1835
  </div>
1810
1836
  <div class="loop-detail-info">
1811
1837
  <h2 class="loop-detail-name">{{ selectedLoop.name }}</h2>
1812
1838
  <div class="loop-detail-meta">
1813
1839
  <span class="loop-detail-schedule">{{ loopScheduleDisplay(selectedLoop) }}</span>
1814
- <span :class="['loop-status-badge', selectedLoop.enabled ? 'loop-status-enabled' : 'loop-status-disabled']">{{ selectedLoop.enabled ? 'Enabled' : 'Disabled' }}</span>
1840
+ <span :class="['loop-status-badge', selectedLoop.enabled ? 'loop-status-enabled' : 'loop-status-disabled']">{{ selectedLoop.enabled ? t('loop.enabled') : t('loop.disabled') }}</span>
1815
1841
  </div>
1816
1842
  <div class="loop-detail-actions">
1817
- <button class="loop-action-btn" @click="startEditingLoop(selectedLoop); selectedLoop = null">Edit</button>
1818
- <button class="loop-action-btn loop-action-run" @click="runNow(selectedLoop.id)" :disabled="isLoopRunning(selectedLoop.id)">Run Now</button>
1819
- <button class="loop-action-btn" @click="toggleLoop(selectedLoop.id)">{{ selectedLoop.enabled ? 'Disable' : 'Enable' }}</button>
1843
+ <button class="loop-action-btn" @click="startEditingLoop(selectedLoop); selectedLoop = null">{{ t('loop.edit') }}</button>
1844
+ <button class="loop-action-btn loop-action-run" @click="runNow(selectedLoop.id)" :disabled="isLoopRunning(selectedLoop.id)">{{ t('loop.runNow') }}</button>
1845
+ <button class="loop-action-btn" @click="toggleLoop(selectedLoop.id)">{{ selectedLoop.enabled ? t('loop.disable') : t('loop.enable') }}</button>
1820
1846
  </div>
1821
1847
  </div>
1822
1848
 
1823
1849
  <div class="loop-detail-prompt-section">
1824
- <div class="loop-detail-prompt-label">Prompt</div>
1850
+ <div class="loop-detail-prompt-label">{{ t('loop.prompt') }}</div>
1825
1851
  <div class="loop-detail-prompt-text">{{ selectedLoop.prompt }}</div>
1826
1852
  </div>
1827
1853
 
1828
1854
  <div class="loop-exec-history-section">
1829
- <div class="loop-exec-history-header">Execution History</div>
1855
+ <div class="loop-exec-history-header">{{ t('loop.executionHistory') }}</div>
1830
1856
  <div v-if="loadingExecutions" class="loop-loading">
1831
1857
  <div class="history-loading-spinner"></div>
1832
- <span>Loading executions...</span>
1858
+ <span>{{ t('loop.loadingExecutions') }}</span>
1833
1859
  </div>
1834
- <div v-else-if="executionHistory.length === 0" class="loop-exec-empty">No executions yet.</div>
1860
+ <div v-else-if="executionHistory.length === 0" class="loop-exec-empty">{{ t('loop.noExecutions') }}</div>
1835
1861
  <div v-else class="loop-exec-list">
1836
1862
  <div v-for="exec in executionHistory" :key="exec.id" class="loop-exec-item">
1837
1863
  <div class="loop-exec-item-left">
@@ -1843,21 +1869,21 @@ const App = {
1843
1869
  <template v-else>?</template>
1844
1870
  </span>
1845
1871
  <span class="loop-exec-time">{{ formatExecTime(exec.startedAt) }}</span>
1846
- <span v-if="exec.status === 'running'" class="loop-exec-running-label">Running...</span>
1872
+ <span v-if="exec.status === 'running'" class="loop-exec-running-label">{{ t('loop.running') }}</span>
1847
1873
  <span v-else-if="exec.durationMs" class="loop-exec-duration">{{ formatDuration(exec.durationMs) }}</span>
1848
1874
  <span v-if="exec.error" class="loop-exec-error-text" :title="exec.error">{{ exec.error.length > 40 ? exec.error.slice(0, 40) + '...' : exec.error }}</span>
1849
- <span v-if="exec.trigger === 'manual'" class="loop-exec-trigger-badge">manual</span>
1875
+ <span v-if="exec.trigger === 'manual'" class="loop-exec-trigger-badge">{{ t('loop.manualBadge') }}</span>
1850
1876
  </div>
1851
1877
  <div class="loop-exec-item-right">
1852
- <button v-if="exec.status === 'running'" class="loop-action-btn" @click="viewExecution(selectedLoop.id, exec.id)">View</button>
1853
- <button v-if="exec.status === 'running'" class="loop-action-btn loop-action-cancel" @click="cancelLoopExecution(selectedLoop.id)">Cancel</button>
1854
- <button v-if="exec.status !== 'running'" class="loop-action-btn" @click="viewExecution(selectedLoop.id, exec.id)">View</button>
1878
+ <button v-if="exec.status === 'running'" class="loop-action-btn" @click="viewExecution(selectedLoop.id, exec.id)">{{ t('loop.view') }}</button>
1879
+ <button v-if="exec.status === 'running'" class="loop-action-btn loop-action-cancel" @click="cancelLoopExecution(selectedLoop.id)">{{ t('loop.cancelExec') }}</button>
1880
+ <button v-if="exec.status !== 'running'" class="loop-action-btn" @click="viewExecution(selectedLoop.id, exec.id)">{{ t('loop.view') }}</button>
1855
1881
  </div>
1856
1882
  </div>
1857
1883
  <!-- Load more executions -->
1858
1884
  <div v-if="hasMoreExecutions && !loadingExecutions" class="loop-load-more">
1859
1885
  <button class="loop-action-btn" :disabled="loadingMoreExecutions" @click="loadMoreExecutions()">
1860
- {{ loadingMoreExecutions ? 'Loading...' : 'Load more' }}
1886
+ {{ loadingMoreExecutions ? t('filePanel.loading') : t('loop.loadMore') }}
1861
1887
  </button>
1862
1888
  </div>
1863
1889
  </div>
@@ -1870,13 +1896,13 @@ const App = {
1870
1896
  <div class="team-create-inner">
1871
1897
  <div class="team-create-header">
1872
1898
  <svg viewBox="0 0 24 24" width="28" height="28"><path fill="currentColor" opacity="0.5" d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 0 0 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
1873
- <h2>{{ editingLoopId ? 'Edit Loop' : 'Create a Scheduled Loop' }}</h2>
1899
+ <h2>{{ editingLoopId ? t('loop.editLoop') : t('loop.createLoop') }}</h2>
1874
1900
  </div>
1875
- <p class="team-create-desc">Configure recurring tasks that run on a schedule. Select a template or create your own.</p>
1901
+ <p class="team-create-desc">{{ t('loop.createDesc') }}</p>
1876
1902
 
1877
1903
  <!-- Template cards -->
1878
1904
  <div v-if="!editingLoopId" class="team-examples-section" style="margin-top: 0;">
1879
- <div class="team-examples-header">Templates</div>
1905
+ <div class="team-examples-header">{{ t('loop.templates') }}</div>
1880
1906
  <div class="team-examples-list">
1881
1907
  <div v-for="key in LOOP_TEMPLATE_KEYS" :key="key"
1882
1908
  :class="['team-example-card', { 'loop-template-selected': loopSelectedTemplate === key }]"
@@ -1890,50 +1916,50 @@ const App = {
1890
1916
  <div class="team-example-title">{{ LOOP_TEMPLATES[key].label }}</div>
1891
1917
  <div class="team-example-text">{{ LOOP_TEMPLATES[key].description }}</div>
1892
1918
  </div>
1893
- <button class="team-example-try" @click="selectLoopTemplate(key)">Try it</button>
1919
+ <button class="team-example-try" @click="selectLoopTemplate(key)">{{ t('team.tryIt') }}</button>
1894
1920
  </div>
1895
1921
  </div>
1896
1922
  </div>
1897
1923
 
1898
1924
  <!-- Name field -->
1899
1925
  <div class="team-tpl-section">
1900
- <label class="team-tpl-label">Name</label>
1926
+ <label class="team-tpl-label">{{ t('loop.name') }}</label>
1901
1927
  <input
1902
1928
  v-model="loopName"
1903
1929
  type="text"
1904
1930
  class="loop-name-input"
1905
- placeholder="e.g. Daily Code Review"
1931
+ :placeholder="t('loop.namePlaceholder')"
1906
1932
  />
1907
1933
  </div>
1908
1934
 
1909
1935
  <!-- Prompt field -->
1910
1936
  <div class="team-tpl-section">
1911
- <label class="team-tpl-label">Prompt</label>
1937
+ <label class="team-tpl-label">{{ t('loop.prompt') }}</label>
1912
1938
  <textarea
1913
1939
  v-model="loopPrompt"
1914
1940
  class="team-create-textarea"
1915
- placeholder="Describe what the Loop should do each time it runs..."
1941
+ :placeholder="t('loop.promptPlaceholder')"
1916
1942
  rows="5"
1917
1943
  ></textarea>
1918
1944
  </div>
1919
1945
 
1920
1946
  <!-- Schedule selector -->
1921
1947
  <div class="team-tpl-section">
1922
- <label class="team-tpl-label">Schedule</label>
1948
+ <label class="team-tpl-label">{{ t('loop.schedule') }}</label>
1923
1949
  <div class="loop-schedule-options">
1924
1950
  <label class="loop-schedule-radio">
1925
1951
  <input type="radio" v-model="loopScheduleType" value="manual" />
1926
- <span>Manual</span>
1927
- <span v-if="loopScheduleType === 'manual'" class="loop-schedule-detail" style="opacity:0.6">run only when triggered</span>
1952
+ <span>{{ t('loop.manual') }}</span>
1953
+ <span v-if="loopScheduleType === 'manual'" class="loop-schedule-detail" style="opacity:0.6">{{ t('loop.manualDetail') }}</span>
1928
1954
  </label>
1929
1955
  <label class="loop-schedule-radio">
1930
1956
  <input type="radio" v-model="loopScheduleType" value="hourly" />
1931
- <span>Every hour</span>
1957
+ <span>{{ t('loop.everyHour') }}</span>
1932
1958
  <span v-if="loopScheduleType === 'hourly'" class="loop-schedule-detail">at minute {{ padTwo(loopScheduleMinute) }}</span>
1933
1959
  </label>
1934
1960
  <label class="loop-schedule-radio">
1935
1961
  <input type="radio" v-model="loopScheduleType" value="daily" />
1936
- <span>Every day</span>
1962
+ <span>{{ t('loop.everyDay') }}</span>
1937
1963
  <span v-if="loopScheduleType === 'daily'" class="loop-schedule-detail">
1938
1964
  at
1939
1965
  <input type="number" v-model.number="loopScheduleHour" min="0" max="23" class="loop-time-input" />
@@ -1943,7 +1969,7 @@ const App = {
1943
1969
  </label>
1944
1970
  <label class="loop-schedule-radio">
1945
1971
  <input type="radio" v-model="loopScheduleType" value="cron" />
1946
- <span>Advanced (cron)</span>
1972
+ <span>{{ t('loop.advancedCron') }}</span>
1947
1973
  <span v-if="loopScheduleType === 'cron'" class="loop-schedule-detail">
1948
1974
  <input type="text" v-model="loopCronExpr" class="loop-cron-input" placeholder="0 9 * * *" />
1949
1975
  </span>
@@ -1954,14 +1980,14 @@ const App = {
1954
1980
  <!-- Action buttons -->
1955
1981
  <div class="team-create-actions">
1956
1982
  <button v-if="editingLoopId" class="team-create-launch" :disabled="!loopName.trim() || !loopPrompt.trim()" @click="saveLoopEdits()">
1957
- Save Changes
1983
+ {{ t('loop.saveChanges') }}
1958
1984
  </button>
1959
1985
  <button v-else class="team-create-launch" :disabled="!loopName.trim() || !loopPrompt.trim()" @click="createLoopFromPanel()">
1960
1986
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 0 0 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
1961
- Create Loop
1987
+ {{ t('loop.createLoopBtn') }}
1962
1988
  </button>
1963
- <button v-if="editingLoopId" class="team-create-cancel" @click="cancelEditingLoop()">Cancel</button>
1964
- <button class="team-create-cancel" @click="backToChat()">Back to Chat</button>
1989
+ <button v-if="editingLoopId" class="team-create-cancel" @click="cancelEditingLoop()">{{ t('loop.cancel') }}</button>
1990
+ <button class="team-create-cancel" @click="backToChat()">{{ t('loop.backToChat') }}</button>
1965
1991
  </div>
1966
1992
 
1967
1993
  <!-- Error message -->
@@ -1973,7 +1999,7 @@ const App = {
1973
1999
 
1974
2000
  <!-- Active Loops list -->
1975
2001
  <div v-if="loopsList.length > 0" class="loop-active-section">
1976
- <div class="loop-active-header">Active Loops</div>
2002
+ <div class="loop-active-header">{{ t('loop.activeLoops') }}</div>
1977
2003
  <div class="loop-active-list">
1978
2004
  <div v-for="l in loopsList" :key="l.id" class="loop-active-item">
1979
2005
  <div class="loop-active-item-info" @click="viewLoop(l.id)">
@@ -1986,14 +2012,14 @@ const App = {
1986
2012
  <span v-if="l.lastExecution" class="loop-active-item-last">
1987
2013
  Last: {{ loopLastRunDisplay(l) }}
1988
2014
  </span>
1989
- <span v-if="isLoopRunning(l.id)" class="loop-exec-running-label">Running...</span>
2015
+ <span v-if="isLoopRunning(l.id)" class="loop-exec-running-label">{{ t('loop.running') }}</span>
1990
2016
  </div>
1991
2017
  </div>
1992
2018
  <div class="loop-active-item-actions">
1993
- <button class="loop-action-btn loop-action-sm" @click="startEditingLoop(l)" title="Edit">Edit</button>
1994
- <button class="loop-action-btn loop-action-sm loop-action-run" @click="runNow(l.id)" :disabled="isLoopRunning(l.id)" title="Run now">Run</button>
1995
- <button class="loop-action-btn loop-action-sm" @click="toggleLoop(l.id)" :title="l.enabled ? 'Disable' : 'Enable'">{{ l.enabled ? 'Pause' : 'Resume' }}</button>
1996
- <button v-if="!l.enabled" class="loop-action-btn loop-action-sm loop-action-delete" @click="requestDeleteLoop(l)" title="Delete">Del</button>
2019
+ <button class="loop-action-btn loop-action-sm" @click="startEditingLoop(l)" :title="t('loop.edit')">{{ t('loop.edit') }}</button>
2020
+ <button class="loop-action-btn loop-action-sm loop-action-run" @click="runNow(l.id)" :disabled="isLoopRunning(l.id)" :title="t('loop.runNow')">{{ t('loop.run') }}</button>
2021
+ <button class="loop-action-btn loop-action-sm" @click="toggleLoop(l.id)" :title="l.enabled ? t('loop.disable') : t('loop.enable')">{{ l.enabled ? t('loop.pause') : t('loop.resume') }}</button>
2022
+ <button v-if="!l.enabled" class="loop-action-btn loop-action-sm loop-action-delete" @click="requestDeleteLoop(l)" :title="t('loop.deleteLoop')">{{ t('loop.del') }}</button>
1997
2023
  </div>
1998
2024
  </div>
1999
2025
  </div>
@@ -2004,18 +2030,18 @@ const App = {
2004
2030
  <!-- Running Loop notification banner -->
2005
2031
  <div v-if="hasRunningLoop && !selectedLoop" class="loop-running-banner">
2006
2032
  <span class="loop-running-banner-dot"></span>
2007
- <span>{{ firstRunningLoop.name }} is running...</span>
2008
- <button class="loop-action-btn loop-action-sm" @click="viewLoop(firstRunningLoop.loopId)">View</button>
2033
+ <span>{{ firstRunningLoop.name }} {{ t('loop.isRunning') }}</span>
2034
+ <button class="loop-action-btn loop-action-sm" @click="viewLoop(firstRunningLoop.loopId)">{{ t('loop.view') }}</button>
2009
2035
  </div>
2010
2036
 
2011
2037
  <!-- Loop delete confirm dialog -->
2012
2038
  <div v-if="loopDeleteConfirmOpen" class="modal-overlay" @click.self="cancelDeleteLoop()">
2013
2039
  <div class="modal-dialog">
2014
- <div class="modal-title">Delete Loop</div>
2015
- <div class="modal-body">Are you sure you want to delete <strong>{{ loopDeleteConfirmName }}</strong>? This cannot be undone.</div>
2040
+ <div class="modal-title">{{ t('loop.deleteLoop') }}</div>
2041
+ <div class="modal-body" v-html="t('loop.deleteConfirm', { name: loopDeleteConfirmName })"></div>
2016
2042
  <div class="modal-actions">
2017
- <button class="modal-confirm-btn" @click="confirmDeleteLoop()">Delete</button>
2018
- <button class="modal-cancel-btn" @click="cancelDeleteLoop()">Cancel</button>
2043
+ <button class="modal-confirm-btn" @click="confirmDeleteLoop()">{{ t('loop.delete') }}</button>
2044
+ <button class="modal-cancel-btn" @click="cancelDeleteLoop()">{{ t('loop.cancel') }}</button>
2019
2045
  </div>
2020
2046
  </div>
2021
2047
  </div>
@@ -2029,25 +2055,25 @@ const App = {
2029
2055
  <div class="empty-state-icon">
2030
2056
  <svg viewBox="0 0 24 24" width="48" height="48"><path fill="currentColor" opacity="0.4" d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
2031
2057
  </div>
2032
- <p>Connected to <strong>{{ agentName }}</strong></p>
2058
+ <p>{{ t('chat.connectedTo') }} <strong>{{ agentName }}</strong></p>
2033
2059
  <p class="muted">{{ workDir }}</p>
2034
- <p class="muted" style="margin-top: 0.5rem;">Send a message to start.</p>
2060
+ <p class="muted" style="margin-top: 0.5rem;">{{ t('chat.sendToStart') }}</p>
2035
2061
  </div>
2036
2062
 
2037
2063
  <div v-if="loadingHistory" class="history-loading">
2038
2064
  <div class="history-loading-spinner"></div>
2039
- <span>Loading conversation history...</span>
2065
+ <span>{{ t('chat.loadingHistory') }}</span>
2040
2066
  </div>
2041
2067
 
2042
2068
  <div v-if="hasMoreMessages" class="load-more-wrapper">
2043
- <button class="load-more-btn" @click="loadMoreMessages">Load earlier messages</button>
2069
+ <button class="load-more-btn" @click="loadMoreMessages">{{ t('chat.loadEarlier') }}</button>
2044
2070
  </div>
2045
2071
 
2046
2072
  <div v-for="(msg, msgIdx) in visibleMessages" :key="msg.id" :class="['message', 'message-' + msg.role]">
2047
2073
 
2048
2074
  <!-- User message -->
2049
2075
  <template v-if="msg.role === 'user'">
2050
- <div class="message-role-label user-label">You</div>
2076
+ <div class="message-role-label user-label">{{ t('chat.you') }}</div>
2051
2077
  <div class="message-bubble user-bubble" :title="formatTimestamp(msg.timestamp)">
2052
2078
  <div class="message-content">{{ msg.content }}</div>
2053
2079
  <div v-if="msg.attachments && msg.attachments.length" class="message-attachments">
@@ -2064,10 +2090,10 @@ const App = {
2064
2090
 
2065
2091
  <!-- Assistant message (markdown) -->
2066
2092
  <template v-else-if="msg.role === 'assistant'">
2067
- <div v-if="!isPrevAssistant(msgIdx)" class="message-role-label assistant-label">Claude</div>
2093
+ <div v-if="!isPrevAssistant(msgIdx)" class="message-role-label assistant-label">{{ t('chat.claude') }}</div>
2068
2094
  <div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]" :title="formatTimestamp(msg.timestamp)">
2069
2095
  <div class="message-actions">
2070
- <button class="icon-btn" @click="copyMessage(msg)" :title="msg.copied ? 'Copied!' : 'Copy'">
2096
+ <button class="icon-btn" @click="copyMessage(msg)" :title="msg.copied ? t('chat.copied') : t('chat.copy')">
2071
2097
  <svg v-if="!msg.copied" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
2072
2098
  <svg v-else viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
2073
2099
  </button>
@@ -2093,7 +2119,7 @@ const App = {
2093
2119
  <div v-show="msg.expanded" class="tool-expand team-agent-tool-expand">
2094
2120
  <pre v-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
2095
2121
  <div v-if="msg.toolOutput" class="team-agent-tool-result">
2096
- <div class="team-agent-tool-result-label">Agent Result</div>
2122
+ <div class="team-agent-tool-result-label">{{ t('team.agentResult') }}</div>
2097
2123
  <div class="team-agent-tool-result-content markdown-body" v-html="getRenderedContent({ role: 'assistant', content: msg.toolOutput })"></div>
2098
2124
  </div>
2099
2125
  </div>
@@ -2143,7 +2169,7 @@ const App = {
2143
2169
  <input
2144
2170
  type="text"
2145
2171
  v-model="msg.customTexts[qi]"
2146
- placeholder="Or type a custom response..."
2172
+ :placeholder="t('chat.customResponse')"
2147
2173
  @input="msg.selectedAnswers[qi] = q.multiSelect ? [] : null"
2148
2174
  @keydown.enter="hasQuestionAnswer(msg) && submitQuestionAnswer(msg)"
2149
2175
  />
@@ -2151,7 +2177,7 @@ const App = {
2151
2177
  </div>
2152
2178
  <div class="ask-question-actions">
2153
2179
  <button class="ask-question-submit" :disabled="!hasQuestionAnswer(msg)" @click="submitQuestionAnswer(msg)">
2154
- Submit
2180
+ {{ t('chat.submit') }}
2155
2181
  </button>
2156
2182
  </div>
2157
2183
  </div>
@@ -2165,8 +2191,8 @@ const App = {
2165
2191
  <div v-else-if="msg.role === 'context-summary'" class="context-summary-wrapper">
2166
2192
  <div class="context-summary-bar" @click="toggleContextSummary(msg)">
2167
2193
  <svg class="context-summary-icon" viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></svg>
2168
- <span class="context-summary-label">Context continued from previous conversation</span>
2169
- <span class="context-summary-toggle">{{ msg.contextExpanded ? 'Hide' : 'Show' }}</span>
2194
+ <span class="context-summary-label">{{ t('chat.contextContinued') }}</span>
2195
+ <span class="context-summary-toggle">{{ msg.contextExpanded ? t('chat.hide') : t('chat.show') }}</span>
2170
2196
  </div>
2171
2197
  <div v-if="msg.contextExpanded" class="context-summary-body">
2172
2198
  <div class="markdown-body" v-html="getRenderedContent({ role: 'assistant', content: msg.content })"></div>
@@ -2211,7 +2237,7 @@ const App = {
2211
2237
  <svg viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
2212
2238
  {{ qm.attachments.length }}
2213
2239
  </span>
2214
- <button class="queue-item-remove" @click="removeQueuedMessage(qm.id)" title="Remove from queue">&times;</button>
2240
+ <button class="queue-item-remove" @click="removeQueuedMessage(qm.id)" :title="t('input.removeFromQueue')">&times;</button>
2215
2241
  </div>
2216
2242
  </div>
2217
2243
  <div v-if="usageStats" class="usage-bar">{{ formatUsage(usageStats) }}</div>
@@ -2228,7 +2254,7 @@ const App = {
2228
2254
  @input="autoResize"
2229
2255
  @paste="handlePaste"
2230
2256
  :disabled="status !== 'Connected' || isCompacting"
2231
- :placeholder="isCompacting ? 'Context compacting in progress...' : 'Send a message · Enter to send'"
2257
+ :placeholder="isCompacting ? t('input.compacting') : t('input.placeholder')"
2232
2258
  rows="1"
2233
2259
  ></textarea>
2234
2260
  <div v-if="attachments.length > 0" class="attachment-bar">
@@ -2241,17 +2267,17 @@ const App = {
2241
2267
  <div class="attachment-name">{{ att.name }}</div>
2242
2268
  <div class="attachment-size">{{ formatFileSize(att.size) }}</div>
2243
2269
  </div>
2244
- <button class="attachment-remove" @click="removeAttachment(i)" title="Remove">&times;</button>
2270
+ <button class="attachment-remove" @click="removeAttachment(i)" :title="t('input.remove')">&times;</button>
2245
2271
  </div>
2246
2272
  </div>
2247
2273
  <div class="input-bottom-row">
2248
- <button class="attach-btn" @click="triggerFileInput" :disabled="status !== 'Connected' || isCompacting || attachments.length >= 5" title="Attach files">
2274
+ <button class="attach-btn" @click="triggerFileInput" :disabled="status !== 'Connected' || isCompacting || attachments.length >= 5" :title="t('input.attachFiles')">
2249
2275
  <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
2250
2276
  </button>
2251
- <button v-if="isProcessing && !hasInput" @click="cancelExecution" class="send-btn stop-btn" title="Stop generation">
2277
+ <button v-if="isProcessing && !hasInput" @click="cancelExecution" class="send-btn stop-btn" :title="t('input.stopGeneration')">
2252
2278
  <svg viewBox="0 0 24 24" width="14" height="14"><rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor"/></svg>
2253
2279
  </button>
2254
- <button v-else @click="sendMessage" :disabled="!canSend" class="send-btn" title="Send">
2280
+ <button v-else @click="sendMessage" :disabled="!canSend" class="send-btn" :title="t('input.send')">
2255
2281
  <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
2256
2282
  </button>
2257
2283
  </div>
@@ -2267,21 +2293,21 @@ const App = {
2267
2293
  @touchstart="filePreview.onResizeStart($event)"></div>
2268
2294
  <div class="preview-panel-header">
2269
2295
  <span class="preview-panel-filename" :title="previewFile?.filePath">
2270
- {{ previewFile?.fileName || 'Preview' }}
2296
+ {{ previewFile?.fileName || t('preview.preview') }}
2271
2297
  </span>
2272
2298
  <button v-if="previewFile?.content && filePreview.isMarkdownFile(previewFile.fileName)"
2273
2299
  class="preview-md-toggle" :class="{ active: previewMarkdownRendered }"
2274
2300
  @click="previewMarkdownRendered = !previewMarkdownRendered"
2275
- :title="previewMarkdownRendered ? 'Show source' : 'Render markdown'">
2301
+ :title="previewMarkdownRendered ? t('preview.showSource') : t('preview.renderMarkdown')">
2276
2302
  <svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"/></svg>
2277
2303
  </button>
2278
2304
  <span v-if="previewFile" class="preview-panel-size">
2279
2305
  {{ filePreview.formatFileSize(previewFile.totalSize) }}
2280
2306
  </span>
2281
- <button class="preview-panel-close" @click="filePreview.closePreview()" title="Close preview">&times;</button>
2307
+ <button class="preview-panel-close" @click="filePreview.closePreview()" :title="t('preview.closePreview')">&times;</button>
2282
2308
  </div>
2283
2309
  <div class="preview-panel-body">
2284
- <div v-if="previewLoading" class="preview-loading">Loading...</div>
2310
+ <div v-if="previewLoading" class="preview-loading">{{ t('preview.loading') }}</div>
2285
2311
  <div v-else-if="previewFile?.error" class="preview-error">
2286
2312
  {{ previewFile.error }}
2287
2313
  </div>
@@ -2296,14 +2322,14 @@ const App = {
2296
2322
  <div v-else-if="previewFile?.content" class="preview-text-container">
2297
2323
  <pre class="preview-code"><code v-html="filePreview.highlightCode(previewFile.content, previewFile.fileName)"></code></pre>
2298
2324
  <div v-if="previewFile.truncated" class="preview-truncated-notice">
2299
- File truncated showing first 100 KB of {{ filePreview.formatFileSize(previewFile.totalSize) }}
2325
+ {{ t('preview.fileTruncated', { size: filePreview.formatFileSize(previewFile.totalSize) }) }}
2300
2326
  </div>
2301
2327
  </div>
2302
2328
  <div v-else-if="previewFile && !previewFile.content && !previewFile.error" class="preview-binary-info">
2303
2329
  <div class="preview-binary-icon">
2304
2330
  <svg viewBox="0 0 24 24" width="48" height="48"><path fill="currentColor" opacity="0.4" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"/></svg>
2305
2331
  </div>
2306
- <p>Binary file</p>
2332
+ <p>{{ t('preview.binaryFile') }}</p>
2307
2333
  <p class="preview-binary-meta">{{ previewFile.mimeType }}</p>
2308
2334
  <p class="preview-binary-meta">{{ filePreview.formatFileSize(previewFile.totalSize) }}</p>
2309
2335
  </div>
@@ -2317,20 +2343,19 @@ const App = {
2317
2343
  <div class="folder-picker-overlay" v-if="folderPickerOpen" @click.self="folderPickerOpen = false">
2318
2344
  <div class="folder-picker-dialog">
2319
2345
  <div class="folder-picker-header">
2320
- <span>Select Working Directory</span>
2346
+ <span>{{ t('folderPicker.title') }}</span>
2321
2347
  <button class="folder-picker-close" @click="folderPickerOpen = false">&times;</button>
2322
2348
  </div>
2323
2349
  <div class="folder-picker-nav">
2324
- <button class="folder-picker-up" @click="folderPickerNavigateUp" :disabled="!folderPickerPath" title="Go to parent directory">
2350
+ <button class="folder-picker-up" @click="folderPickerNavigateUp" :disabled="!folderPickerPath" :title="t('folderPicker.parentDir')">
2325
2351
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
2326
2352
  </button>
2327
- <input class="folder-picker-path-input" type="text" v-model="folderPickerPath" @keydown.enter="folderPickerGoToPath" placeholder="Enter path..." spellcheck="false" />
2353
+ <input class="folder-picker-path-input" type="text" v-model="folderPickerPath" @keydown.enter="folderPickerGoToPath" :placeholder="t('folderPicker.pathPlaceholder')" spellcheck="false" />
2328
2354
  </div>
2329
2355
  <div class="folder-picker-list">
2330
2356
  <div v-if="folderPickerLoading" class="folder-picker-loading">
2331
2357
  <div class="history-loading-spinner"></div>
2332
- <span>Loading...</span>
2333
- </div>
2358
+ <span>{{ t('preview.loading') }}</span> </div>
2334
2359
  <template v-else>
2335
2360
  <div
2336
2361
  v-for="entry in folderPickerEntries" :key="entry.name"
@@ -2341,12 +2366,12 @@ const App = {
2341
2366
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
2342
2367
  <span>{{ entry.name }}</span>
2343
2368
  </div>
2344
- <div v-if="folderPickerEntries.length === 0" class="folder-picker-empty">No subdirectories found.</div>
2369
+ <div v-if="folderPickerEntries.length === 0" class="folder-picker-empty">{{ t('folderPicker.noSubdirs') }}</div>
2345
2370
  </template>
2346
2371
  </div>
2347
2372
  <div class="folder-picker-footer">
2348
- <button class="folder-picker-cancel" @click="folderPickerOpen = false">Cancel</button>
2349
- <button class="folder-picker-confirm" @click="confirmFolderPicker" :disabled="!folderPickerPath">Open</button>
2373
+ <button class="folder-picker-cancel" @click="folderPickerOpen = false">{{ t('folderPicker.cancel') }}</button>
2374
+ <button class="folder-picker-confirm" @click="confirmFolderPicker" :disabled="!folderPickerPath">{{ t('folderPicker.open') }}</button>
2350
2375
  </div>
2351
2376
  </div>
2352
2377
  </div>
@@ -2354,15 +2379,15 @@ const App = {
2354
2379
  <!-- Delete Session Confirmation Dialog -->
2355
2380
  <div class="folder-picker-overlay" v-if="deleteConfirmOpen" @click.self="cancelDeleteSession">
2356
2381
  <div class="delete-confirm-dialog">
2357
- <div class="delete-confirm-header">Delete Session</div>
2382
+ <div class="delete-confirm-header">{{ t('dialog.deleteSession') }}</div>
2358
2383
  <div class="delete-confirm-body">
2359
- <p>Are you sure you want to delete this session?</p>
2384
+ <p>{{ t('dialog.deleteSessionConfirm') }}</p>
2360
2385
  <p class="delete-confirm-title">{{ deleteConfirmTitle }}</p>
2361
- <p class="delete-confirm-warning">This action cannot be undone.</p>
2386
+ <p class="delete-confirm-warning">{{ t('dialog.cannotUndo') }}</p>
2362
2387
  </div>
2363
2388
  <div class="delete-confirm-footer">
2364
- <button class="folder-picker-cancel" @click="cancelDeleteSession">Cancel</button>
2365
- <button class="delete-confirm-btn" @click="confirmDeleteSession">Delete</button>
2389
+ <button class="folder-picker-cancel" @click="cancelDeleteSession">{{ t('dialog.cancel') }}</button>
2390
+ <button class="delete-confirm-btn" @click="confirmDeleteSession">{{ t('dialog.delete') }}</button>
2366
2391
  </div>
2367
2392
  </div>
2368
2393
  </div>
@@ -2370,15 +2395,15 @@ const App = {
2370
2395
  <!-- Delete Team Confirmation Dialog -->
2371
2396
  <div class="folder-picker-overlay" v-if="deleteTeamConfirmOpen" @click.self="cancelDeleteTeam">
2372
2397
  <div class="delete-confirm-dialog">
2373
- <div class="delete-confirm-header">Delete Team</div>
2398
+ <div class="delete-confirm-header">{{ t('dialog.deleteTeam') }}</div>
2374
2399
  <div class="delete-confirm-body">
2375
- <p>Are you sure you want to delete this team?</p>
2400
+ <p>{{ t('dialog.deleteTeamConfirm') }}</p>
2376
2401
  <p class="delete-confirm-title">{{ deleteTeamConfirmTitle }}</p>
2377
- <p class="delete-confirm-warning">This action cannot be undone.</p>
2402
+ <p class="delete-confirm-warning">{{ t('dialog.cannotUndo') }}</p>
2378
2403
  </div>
2379
2404
  <div class="delete-confirm-footer">
2380
- <button class="folder-picker-cancel" @click="cancelDeleteTeam">Cancel</button>
2381
- <button class="delete-confirm-btn" @click="confirmDeleteTeam">Delete</button>
2405
+ <button class="folder-picker-cancel" @click="cancelDeleteTeam">{{ t('dialog.cancel') }}</button>
2406
+ <button class="delete-confirm-btn" @click="confirmDeleteTeam">{{ t('dialog.delete') }}</button>
2382
2407
  </div>
2383
2408
  </div>
2384
2409
  </div>
@@ -2388,23 +2413,23 @@ const App = {
2388
2413
  <div class="auth-dialog">
2389
2414
  <div class="auth-dialog-header">
2390
2415
  <svg viewBox="0 0 24 24" width="22" height="22"><path fill="currentColor" d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg>
2391
- <span>Session Protected</span>
2416
+ <span>{{ t('auth.sessionProtected') }}</span>
2392
2417
  </div>
2393
2418
  <div class="auth-dialog-body">
2394
- <p>This session requires a password to access.</p>
2419
+ <p>{{ t('auth.passwordRequired') }}</p>
2395
2420
  <input
2396
2421
  type="password"
2397
2422
  class="auth-password-input"
2398
2423
  v-model="authPassword"
2399
2424
  @keydown.enter="submitPassword"
2400
- placeholder="Enter password..."
2425
+ :placeholder="t('auth.passwordPlaceholder')"
2401
2426
  autofocus
2402
2427
  />
2403
2428
  <p v-if="authError" class="auth-error">{{ authError }}</p>
2404
2429
  <p v-if="authAttempts" class="auth-attempts">{{ authAttempts }}</p>
2405
2430
  </div>
2406
2431
  <div class="auth-dialog-footer">
2407
- <button class="auth-submit-btn" @click="submitPassword" :disabled="!authPassword.trim()">Unlock</button>
2432
+ <button class="auth-submit-btn" @click="submitPassword" :disabled="!authPassword.trim()">{{ t('auth.unlock') }}</button>
2408
2433
  </div>
2409
2434
  </div>
2410
2435
  </div>
@@ -2414,11 +2439,11 @@ const App = {
2414
2439
  <div class="auth-dialog auth-dialog-locked">
2415
2440
  <div class="auth-dialog-header">
2416
2441
  <svg viewBox="0 0 24 24" width="22" height="22"><path fill="currentColor" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg>
2417
- <span>Access Locked</span>
2442
+ <span>{{ t('auth.accessLocked') }}</span>
2418
2443
  </div>
2419
2444
  <div class="auth-dialog-body">
2420
2445
  <p>{{ authError }}</p>
2421
- <p class="auth-locked-hint">Close this tab and try again later.</p>
2446
+ <p class="auth-locked-hint">{{ t('auth.tryAgainLater') }}</p>
2422
2447
  </div>
2423
2448
  </div>
2424
2449
  </div>
@@ -2427,7 +2452,7 @@ const App = {
2427
2452
  <Transition name="fade">
2428
2453
  <div v-if="workdirSwitching" class="workdir-switching-overlay">
2429
2454
  <div class="workdir-switching-spinner"></div>
2430
- <div class="workdir-switching-text">Switching directory...</div>
2455
+ <div class="workdir-switching-text">{{ t('workdir.switching') }}</div>
2431
2456
  </div>
2432
2457
  </Transition>
2433
2458
 
@@ -2439,15 +2464,15 @@ const App = {
2439
2464
  >
2440
2465
  <div class="file-context-item" @click="fileBrowser.askClaudeRead()">
2441
2466
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 15h14v2H5zm0-4h14v2H5zm0-4h14v2H5z"/></svg>
2442
- Ask Claude to read
2467
+ {{ t('contextMenu.askClaudeRead') }}
2443
2468
  </div>
2444
2469
  <div class="file-context-item" @click="fileBrowser.copyPath()">
2445
2470
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
2446
- {{ fileContextMenu.copied ? 'Copied!' : 'Copy path' }}
2471
+ {{ fileContextMenu.copied ? t('contextMenu.copied') : t('contextMenu.copyPath') }}
2447
2472
  </div>
2448
2473
  <div class="file-context-item" @click="fileBrowser.insertPath()">
2449
2474
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>
2450
- Insert path to input
2475
+ {{ t('contextMenu.insertPath') }}
2451
2476
  </div>
2452
2477
  </div>
2453
2478
  </div>