Skip to content

Commit 7abeb50

Browse files
committed
feat(web): translate remaining ui copy
1 parent 1dc4f9f commit 7abeb50

44 files changed

Lines changed: 689 additions & 298 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/web/src/features/agent-panes/views/shared/draft-launcher.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,11 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
312312
>
313313
{isFileDropTarget ? (
314314
<div className="pane-drop-overlay pane-drop-overlay--draft">
315-
<div className="pane-drop-overlay__center">Open in editor</div>
315+
<div className="pane-drop-overlay__center">{t("agent_panes.open_in_editor")}</div>
316316
</div>
317317
) : paneDropOverlayPlacement ? (
318318
<div className={`pane-drop-overlay pane-drop-overlay--${paneDropOverlayPlacement}`}>
319-
<div className="pane-drop-overlay__center">Move here</div>
319+
<div className="pane-drop-overlay__center">{t("agent_panes.move_here")}</div>
320320
</div>
321321
) : null}
322322

@@ -325,20 +325,21 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
325325
<StatusDot tone="neutral" className="session-dot session-dot-idle" />
326326
<div className="session-header-copy">
327327
<div className="session-title-row">
328-
<span className="session-title">{t("session.provider_select") || "New Session"}</span>
328+
<span className="session-title">{t("session.provider_select")}</span>
329329
<Tag color="neutral" className="session-state-badge">
330-
DRAFT
330+
{t("session.state.draft")}
331331
</Tag>
332332
</div>
333333
</div>
334334
</div>
335335

336336
<div className="session-header-actions">
337337
{canDragPane ? (
338-
<Tooltip content="Drag pane">
338+
<Tooltip content={t("agent_panes.drag_pane")}>
339339
<IconButton
340-
aria-label="Drag pane"
340+
aria-label={t("agent_panes.drag_pane")}
341341
className="session-action-btn session-action-btn-drag"
342+
data-session-action="drag"
342343
icon={<GripVertical size={13} />}
343344
onPointerDown={(event) => {
344345
event.preventDefault();
@@ -358,28 +359,31 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
358359
/>
359360
</Tooltip>
360361
) : null}
361-
<Tooltip content="Split horizontal">
362+
<Tooltip content={t("agent_panes.split_horizontal")}>
362363
<IconButton
363-
aria-label="Split horizontal"
364+
aria-label={t("agent_panes.split_horizontal")}
364365
className="session-action-btn"
366+
data-session-action="split-horizontal"
365367
icon={<FlipHorizontal size={13} />}
366368
onClick={handleSplitHorizontal}
367369
size="sm"
368370
/>
369371
</Tooltip>
370-
<Tooltip content="Split vertical">
372+
<Tooltip content={t("agent_panes.split_vertical")}>
371373
<IconButton
372-
aria-label="Split vertical"
374+
aria-label={t("agent_panes.split_vertical")}
373375
className="session-action-btn"
376+
data-session-action="split-vertical"
374377
icon={<FlipVertical size={13} />}
375378
onClick={handleSplitVertical}
376379
size="sm"
377380
/>
378381
</Tooltip>
379-
<Tooltip content="Close">
382+
<Tooltip content={t("action.close")}>
380383
<IconButton
381-
aria-label="Close"
384+
aria-label={t("action.close")}
382385
className="session-action-btn session-action-btn-close"
386+
data-session-action="close"
383387
icon={<X size={14} />}
384388
onClick={handleClosePane}
385389
size="sm"
@@ -397,13 +401,12 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
397401
onPointerDown={handleCarouselPointerDown}
398402
onPointerUp={handleCarouselPointerUp}
399403
>
400-
{/* Agent panel */}
401404
<div className="agent-draft-panel">
402405
<div className="agent-draft-panel-header">
403406
<span className="agent-draft-panel-icon">
404407
<ThemedIcon semantic="agent.provider.claude" size={13} />
405408
</span>
406-
<span className="agent-draft-panel-label">Agent</span>
409+
<span className="agent-draft-panel-label">{t("agent_panes.agent_panel")}</span>
407410
</div>
408411

409412
<div className="agent-draft-panel-list">
@@ -483,7 +486,6 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
483486
</div>
484487
</div>
485488

486-
{/* File Editor panel */}
487489
<div className="agent-draft-panel">
488490
<div className="agent-draft-panel-header">
489491
<span className="agent-draft-panel-icon">
@@ -501,7 +503,7 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
501503
<polyline points="14 2 14 8 20 8" />
502504
</svg>
503505
</span>
504-
<span className="agent-draft-panel-label">File Editor</span>
506+
<span className="agent-draft-panel-label">{t("agent_panes.file_editor")}</span>
505507
</div>
506508

507509
<div className="agent-draft-drop-zone">
@@ -521,15 +523,21 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
521523
<line x1="12" y1="15" x2="12" y2="3" />
522524
</svg>
523525
</div>
524-
<span className="agent-draft-drop-zone-title">拖入文件打开</span>
526+
<span className="agent-draft-drop-zone-title">
527+
{t("agent_panes.drop_file_to_open")}
528+
</span>
525529
</div>
526530
</div>
527531
</div>
528532

529-
<div className="agent-draft-carousel-dots" role="group" aria-label="Draft panels">
533+
<div
534+
className="agent-draft-carousel-dots"
535+
role="group"
536+
aria-label={t("agent_panes.draft_panels")}
537+
>
530538
{[
531-
{ id: "agent" as const, label: "Agent" },
532-
{ id: "file" as const, label: "File Editor" },
539+
{ id: "agent" as const, label: t("agent_panes.agent_panel") },
540+
{ id: "file" as const, label: t("agent_panes.file_editor") },
533541
].map((panel) => (
534542
<button
535543
key={panel.id}
@@ -542,9 +550,7 @@ export const DraftLauncher: FC<DraftLauncherProps> = ({
542550
))}
543551
</div>
544552

545-
<div className="agent-draft-footer">
546-
点击「启动 Agent」,或将文件拖到右侧区域直接打开。
547-
</div>
553+
<div className="agent-draft-footer">{t("agent_panes.draft_footer")}</div>
548554
</div>
549555
</div>
550556
</div>

packages/web/src/features/agent-panes/views/shared/editor-pane-card.test.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,6 @@ const paneDragEnabledMock = vi.hoisted(() => ({
2626
value: true,
2727
}));
2828

29-
vi.mock("../../../../lib/i18n", () => ({
30-
useTranslation: () => (key: string, params?: Record<string, string>) => {
31-
const dictionary: Record<string, string> = {
32-
"action.close": "Close",
33-
"code_editor.unsaved_changes": "Unsaved changes",
34-
"code_editor.close_unsaved_title": "Discard unsaved changes?",
35-
"code_editor.close_unsaved_description": `${params?.name ?? "File"} has unsaved changes.`,
36-
"code_editor.discard_and_close": "Discard and Close",
37-
"common.cancel": "Cancel",
38-
};
39-
return dictionary[key] ?? key;
40-
},
41-
}));
42-
4329
vi.mock("../../actions/use-pane-drag-enabled", () => ({
4430
usePaneDragEnabled: () => paneDragEnabledMock.value,
4531
}));

packages/web/src/features/agent-panes/views/shared/editor-pane-card.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import type { PaneDropPlacement } from "../../actions/pane-drag-types";
1515
import type { PaneDragSourceSnapshot } from "../../actions/use-pane-drag-controller";
1616
import { usePaneDragEnabled } from "../../actions/use-pane-drag-enabled";
1717

18-
function getEditorPaneTitle(path: string | null): string {
18+
function getEditorPaneTitle(path: string | null, fallbackTitle: string): string {
1919
if (!path) {
20-
return "Editor";
20+
return fallbackTitle;
2121
}
2222

2323
const segments = path.split("/");
@@ -52,7 +52,7 @@ export const EditorPaneCard: FC<EditorPaneCardProps> = ({
5252
const editorState = useCodeEditorActions();
5353
const supportsPaneDrag = usePaneDragEnabled();
5454
const canDragPane = supportsPaneDrag && Boolean(onPaneDragStart);
55-
const title = getEditorPaneTitle(activeFilePath);
55+
const title = getEditorPaneTitle(activeFilePath, t("agent_panes.file_editor"));
5656
const activeOpenFile = activeFilePath ? openFiles[activeFilePath] : undefined;
5757
const isDirtyTextFile = activeOpenFile?.kind === "text" && activeOpenFile.isDirty === true;
5858
const dragOverlayPlacement = dragState?.isActiveDropTarget ? dragState.hoverPlacement : null;
@@ -85,7 +85,7 @@ export const EditorPaneCard: FC<EditorPaneCardProps> = ({
8585
{dragOverlayPlacement ? (
8686
<div className={`pane-drop-overlay pane-drop-overlay--${dragOverlayPlacement}`}>
8787
{dragOverlayPlacement === "center" ? (
88-
<div className="pane-drop-overlay__center">Swap</div>
88+
<div className="pane-drop-overlay__center">{t("agent_panes.swap")}</div>
8989
) : null}
9090
</div>
9191
) : null}
@@ -97,10 +97,11 @@ export const EditorPaneCard: FC<EditorPaneCardProps> = ({
9797
actions={
9898
<>
9999
{canDragPane ? (
100-
<Tooltip content="Drag pane">
100+
<Tooltip content={t("agent_panes.drag_pane")}>
101101
<IconButton
102-
aria-label="Drag pane"
102+
aria-label={t("agent_panes.drag_pane")}
103103
className="session-action-btn session-action-btn-drag"
104+
data-session-action="drag"
104105
icon={<GripVertical size={13} />}
105106
onPointerDown={(event) => {
106107
event.preventDefault();
@@ -117,19 +118,21 @@ export const EditorPaneCard: FC<EditorPaneCardProps> = ({
117118
</Tooltip>
118119
) : null}
119120
<CodeEditorDesktopHeaderActions state={editorState} showCloseAction={false} />
120-
<Tooltip content="Split horizontal">
121+
<Tooltip content={t("agent_panes.split_horizontal")}>
121122
<IconButton
122-
aria-label="Split horizontal"
123+
aria-label={t("agent_panes.split_horizontal")}
123124
className="session-action-btn"
125+
data-session-action="split-horizontal"
124126
icon={<FlipHorizontal size={13} />}
125127
onClick={() => onSplitPane(paneId, "horizontal")}
126128
size="sm"
127129
/>
128130
</Tooltip>
129-
<Tooltip content="Split vertical">
131+
<Tooltip content={t("agent_panes.split_vertical")}>
130132
<IconButton
131-
aria-label="Split vertical"
133+
aria-label={t("agent_panes.split_vertical")}
132134
className="session-action-btn"
135+
data-session-action="split-vertical"
133136
icon={<FlipVertical size={13} />}
134137
onClick={() => onSplitPane(paneId, "vertical")}
135138
size="sm"
@@ -139,6 +142,7 @@ export const EditorPaneCard: FC<EditorPaneCardProps> = ({
139142
<IconButton
140143
aria-label={t("action.close")}
141144
className="session-action-btn session-action-btn-close"
145+
data-session-action="close"
142146
icon={<X size={14} />}
143147
onClick={requestClosePane}
144148
size="sm"

packages/web/src/features/agent-panes/views/shared/session-card.tsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { dispatchCommandAtom } from "../../../../atoms/connection";
1515
import { sessionByIdAtomFamily, sessionsAtom } from "../../../../atoms/sessions";
1616
import { workspaceByIdAtomFamily } from "../../../../atoms/workspaces";
1717
import { IconButton, StatusDot, Tag, Tooltip } from "../../../../components/ui";
18+
import { useTranslation } from "../../../../lib/i18n";
1819
import { useTerminalThemeBackground } from "../../../../theme";
1920
import { PanelHeader } from "../../../shared/components/panel-header";
2021
import { useSupervisor } from "../../../supervisor/actions/use-supervisor";
@@ -72,6 +73,7 @@ export const SessionCard: FC<SessionCardProps> = ({
7273
onSplitHorizontal,
7374
onSplitVertical,
7475
}) => {
76+
const t = useTranslation();
7577
const session = useAtomValue(sessionByIdAtomFamily(sessionId));
7678
const dispatch = useAtomValue(dispatchCommandAtom);
7779
const setSessions = useSetAtom(sessionsAtom);
@@ -113,7 +115,7 @@ export const SessionCard: FC<SessionCardProps> = ({
113115

114116
const sessionTitle = session.title?.trim() || formatSessionLabel(session.id);
115117
const providerLabel = formatProviderLabel(session.providerId);
116-
const sessionStateLabel = formatSessionStateLabel(session.state);
118+
const sessionStateLabel = formatSessionStateLabel(session.state, t);
117119
const terminalReadOnly = terminalReadOnlyOverride ?? !isSessionInteractive(session.state);
118120
const isActiveSession = workspace?.uiState.activeSessionId === session.id;
119121
const isRunning = session.state === "running";
@@ -187,7 +189,7 @@ export const SessionCard: FC<SessionCardProps> = ({
187189
{dragOverlayPlacement ? (
188190
<div className={`pane-drop-overlay pane-drop-overlay--${dragOverlayPlacement}`}>
189191
{dragOverlayPlacement === "center" ? (
190-
<div className="pane-drop-overlay__center">Swap</div>
192+
<div className="pane-drop-overlay__center">{t("agent_panes.swap")}</div>
191193
) : null}
192194
</div>
193195
) : null}
@@ -227,10 +229,11 @@ export const SessionCard: FC<SessionCardProps> = ({
227229
{showHeaderActions ? (
228230
<div className="session-header-actions">
229231
{supportsPaneDrag ? (
230-
<Tooltip content="Drag pane">
232+
<Tooltip content={t("agent_panes.drag_pane")}>
231233
<IconButton
232-
aria-label="Drag pane"
234+
aria-label={t("agent_panes.drag_pane")}
233235
className="session-action-btn session-action-btn-drag"
236+
data-session-action="drag"
234237
icon={<GripVertical size={13} />}
235238
onPointerDown={(event) => {
236239
event.preventDefault();
@@ -255,28 +258,31 @@ export const SessionCard: FC<SessionCardProps> = ({
255258
/>
256259
</Tooltip>
257260
) : null}
258-
<Tooltip content="Split horizontal">
261+
<Tooltip content={t("agent_panes.split_horizontal")}>
259262
<IconButton
260-
aria-label="Split horizontal"
263+
aria-label={t("agent_panes.split_horizontal")}
261264
className="session-action-btn"
265+
data-session-action="split-horizontal"
262266
icon={<FlipHorizontal size={13} />}
263267
onClick={() => onSplitHorizontal?.()}
264268
size="sm"
265269
/>
266270
</Tooltip>
267-
<Tooltip content="Split vertical">
271+
<Tooltip content={t("agent_panes.split_vertical")}>
268272
<IconButton
269-
aria-label="Split vertical"
273+
aria-label={t("agent_panes.split_vertical")}
270274
className="session-action-btn"
275+
data-session-action="split-vertical"
271276
icon={<FlipVertical size={13} />}
272277
onClick={() => onSplitVertical?.()}
273278
size="sm"
274279
/>
275280
</Tooltip>
276-
<Tooltip content="Close">
281+
<Tooltip content={t("action.close")}>
277282
<IconButton
278-
aria-label="Close"
283+
aria-label={t("action.close")}
279284
className="session-action-btn session-action-btn-close"
285+
data-session-action="close"
280286
icon={<X size={14} />}
281287
onClick={() => void onClose?.()}
282288
size="sm"
@@ -377,8 +383,15 @@ function formatSessionLabel(sessionId: string) {
377383
return sessionId.replace(/[_-]/g, " ").toUpperCase();
378384
}
379385

380-
function formatSessionStateLabel(state: SessionState) {
381-
return state.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
386+
function formatSessionStateLabel(
387+
state: SessionState,
388+
t: (key: string, params?: Record<string, string | number>) => string
389+
) {
390+
const key = `session.state.${state}`;
391+
const translated = t(key);
392+
return translated === key
393+
? state.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
394+
: translated;
382395
}
383396

384397
function formatProviderLabel(providerId: string) {

packages/web/src/features/agent-providers/actions/use-agent-providers.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { ProviderListItem } from "@coder-studio/core";
22
import { useAtomValue } from "jotai";
3-
import { useEffect, useState } from "react";
3+
import { useCallback, useEffect, useState } from "react";
44
import { dispatchCommandAtom } from "../../../atoms/connection";
5+
import { useTranslation } from "../../../lib/i18n";
56

67
interface UseAgentProvidersResult {
78
providers: ProviderListItem[];
@@ -11,31 +12,32 @@ interface UseAgentProvidersResult {
1112
}
1213

1314
export function useAgentProviders(): UseAgentProvidersResult {
15+
const t = useTranslation();
1416
const dispatch = useAtomValue(dispatchCommandAtom);
1517
const [providers, setProviders] = useState<ProviderListItem[]>([]);
1618
const [isLoading, setIsLoading] = useState(true);
1719
const [error, setError] = useState<string | null>(null);
1820

19-
const refresh = async () => {
21+
const refresh = useCallback(async () => {
2022
setIsLoading(true);
2123
setError(null);
2224

2325
const result = await dispatch<ProviderListItem[]>("provider.list", {});
2426
if (!result.ok || !result.data) {
2527
setProviders([]);
26-
setError(result.error?.message ?? "Failed to load providers");
28+
setError(result.error?.message ?? t("provider.load_failed"));
2729
setIsLoading(false);
2830
return;
2931
}
3032

3133
setProviders(result.data);
3234
setError(null);
3335
setIsLoading(false);
34-
};
36+
}, [dispatch, t]);
3537

3638
useEffect(() => {
3739
void refresh();
38-
}, [dispatch]);
40+
}, [refresh]);
3941

4042
return {
4143
providers,

0 commit comments

Comments
 (0)