diff --git a/src/app/commit/page.tsx b/src/app/commit/page.tsx index 112c3e1..e31e0cb 100644 --- a/src/app/commit/page.tsx +++ b/src/app/commit/page.tsx @@ -2,6 +2,7 @@ import { Suspense, useCallback, useEffect, useState } from "react" import { useSearchParams } from "next/navigation" +import { useTranslations } from "next-intl" import { getCurrentWindow } from "@tauri-apps/api/window" import { Loader2 } from "lucide-react" import { CommitWorkspace } from "@/components/layout/commit-dialog" @@ -19,6 +20,7 @@ interface FolderLoadState { } function CommitPageInner() { + const t = useTranslations("CommitPage") const searchParams = useSearchParams() const [state, setState] = useState({ loadedId: null, @@ -76,7 +78,8 @@ function CommitPageInner() { - Git Commit{hasValidFolderId && folder ? ` · ${folder.name}` : ""} + {t("title")} + {hasValidFolderId && folder ? ` · ${folder.name}` : ""} } /> @@ -84,12 +87,12 @@ function CommitPageInner() {
{!hasValidFolderId ? (
- 缺少有效的 folderId 参数 + {t("invalidFolderId")}
) : loading ? (
- 正在加载仓库信息... + {t("loadingRepo")}
) : error ? (
diff --git a/src/components/ai-elements/message.tsx b/src/components/ai-elements/message.tsx index b9e7702..1794ae9 100644 --- a/src/components/ai-elements/message.tsx +++ b/src/components/ai-elements/message.tsx @@ -12,6 +12,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" import { cjk } from "@streamdown/cjk" import { code } from "@streamdown/code" import { math } from "@streamdown/math" @@ -258,11 +259,12 @@ export const MessageBranchPrevious = ({ children, ...props }: MessageBranchPreviousProps) => { + const t = useTranslations("Folder.chat.messageBranch") const { goToPrevious, totalBranches } = useMessageBranch() return (
) } @@ -148,6 +150,7 @@ export function TerminalStatus({ children, ...props }: TerminalStatusProps) { + const t = useTranslations("Folder.chat.terminal") const { isStreaming } = useContext(TerminalContext) if (!isStreaming) { @@ -162,7 +165,7 @@ export function TerminalStatus({ )} {...props} > - {children ?? Running} + {children ?? {t("running")}} ) } diff --git a/src/components/ai-elements/tool.tsx b/src/components/ai-elements/tool.tsx index 4a8f7bf..a439851 100644 --- a/src/components/ai-elements/tool.tsx +++ b/src/components/ai-elements/tool.tsx @@ -18,6 +18,7 @@ import { WrenchIcon, XCircleIcon, } from "lucide-react" +import { useTranslations } from "next-intl" import { isValidElement } from "react" import { CodeBlock } from "./code-block" @@ -47,16 +48,6 @@ export type ToolHeaderProps = { } ) -const statusLabels: Record = { - "approval-requested": "Awaiting Approval", - "approval-responded": "Responded", - "input-available": "Running", - "input-streaming": "Pending", - "output-available": "Completed", - "output-denied": "Denied", - "output-error": "Error", -} - const statusIcons: Record = { "approval-requested": , "approval-responded": , @@ -67,10 +58,10 @@ const statusIcons: Record = { "output-error": , } -export const getStatusBadge = (status: ToolPart["state"]) => ( +export const getStatusBadge = (status: ToolPart["state"], label: string) => ( {statusIcons[status]} - {statusLabels[status]} + {label} ) @@ -84,8 +75,23 @@ export const ToolHeader = ({ toolName, ...props }: ToolHeaderProps) => { + const t = useTranslations("Folder.chat.tool") const derivedName = type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-") + const statusLabel = + state === "approval-requested" + ? t("status.approvalRequested") + : state === "approval-responded" + ? t("status.approvalResponded") + : state === "input-available" + ? t("status.inputAvailable") + : state === "input-streaming" + ? t("status.inputStreaming") + : state === "output-available" + ? t("status.outputAvailable") + : state === "output-denied" + ? t("status.outputDenied") + : t("status.outputError") return ( {titleSuffix ? {titleSuffix} : null} - {getStatusBadge(state)} + {getStatusBadge(state, statusLabel)} @@ -127,6 +133,7 @@ export type ToolInputProps = ComponentProps<"div"> & { } export const ToolInput = ({ className, input, ...props }: ToolInputProps) => { + const t = useTranslations("Folder.chat.tool") const formattedCode = (() => { if (typeof input === "string") { try { @@ -142,7 +149,7 @@ export const ToolInput = ({ className, input, ...props }: ToolInputProps) => { return (

- Parameters + {t("parameters")}

diff --git a/src/components/message/content-parts-renderer.tsx b/src/components/message/content-parts-renderer.tsx index 64c92b2..39d4a3b 100644 --- a/src/components/message/content-parts-renderer.tsx +++ b/src/components/message/content-parts-renderer.tsx @@ -3,6 +3,7 @@ import type { BundledLanguage } from "shiki" import type { AdaptedContentPart } from "@/lib/adapters/ai-elements-adapter" import type { MessageRole } from "@/lib/types" import { normalizeToolName } from "@/lib/tool-call-normalization" +import { useTranslations } from "next-intl" import { countUnifiedDiffLineChanges, estimateChangedLineStats, @@ -1881,6 +1882,7 @@ const ToolCallPart = memo(function ToolCallPart({ }: { part: Extract }) { + const t = useTranslations("Folder.chat.contentParts") const [manualOpen, setManualOpen] = useState(false) const normalizedToolName = useMemo( () => normalizeToolName(part.toolName), @@ -2046,7 +2048,7 @@ const ToolCallPart = memo(function ToolCallPart({ /> {liveOutputTruncated && (
- Showing tail output while streaming for performance. + {t("showingTailOutput")}
)}
@@ -2068,9 +2070,14 @@ const ToolResultPart = memo(function ToolResultPart({ }: { part: Extract }) { + const t = useTranslations("Folder.chat.contentParts") return ( - + diff --git a/src/components/message/live-turn-stats.tsx b/src/components/message/live-turn-stats.tsx index 3111156..7f12565 100644 --- a/src/components/message/live-turn-stats.tsx +++ b/src/components/message/live-turn-stats.tsx @@ -1,6 +1,7 @@ "use client" import { useEffect, useMemo, useState } from "react" +import { useLocale, useTranslations } from "next-intl" import type { LiveMessage } from "@/contexts/acp-connections-context" import { inferLiveToolName } from "@/lib/tool-call-normalization" import { @@ -9,11 +10,6 @@ import { } from "@/lib/line-change-stats" import { FilePenLine, Timer, Wrench } from "lucide-react" -function formatElapsed(ms: number): string { - if (ms >= 60_000) return `${(ms / 60_000).toFixed(1)}m` - return `${(ms / 1_000).toFixed(1)}s` -} - interface LiveTurnStatsProps { message: LiveMessage } @@ -27,14 +23,9 @@ interface LiveEditStats extends LineChangeStats { files: number } -const COMPACT_NUMBER = new Intl.NumberFormat("en-US", { - notation: "compact", - maximumFractionDigits: 1, -}) - -function formatCompactInt(n: number): string { +function formatCompactInt(n: number, formatter: Intl.NumberFormat): string { if (n < 1000) return String(n) - return COMPACT_NUMBER.format(n).toUpperCase() + return formatter.format(n) } function asObject(value: unknown): Record | null { @@ -267,8 +258,18 @@ function extractLiveEditStats(message: LiveMessage): LiveEditStats { } export function LiveTurnStats({ message }: LiveTurnStatsProps) { + const locale = useLocale() + const t = useTranslations("Folder.chat.liveTurnStats") const [elapsed, setElapsed] = useState(() => Date.now() - message.startedAt) const editStats = useMemo(() => extractLiveEditStats(message), [message]) + const compactNumberFormatter = useMemo( + () => + new Intl.NumberFormat(locale, { + notation: "compact", + maximumFractionDigits: 1, + }), + [locale] + ) useEffect(() => { const timer = setInterval(() => { @@ -295,26 +296,32 @@ export function LiveTurnStats({ message }: LiveTurnStatsProps) { isThinking = true } + const elapsedLabel = + elapsed >= 60_000 + ? t("elapsedMinutes", { value: (elapsed / 60_000).toFixed(1) }) + : t("elapsedSeconds", { value: (elapsed / 1_000).toFixed(1) }) + return (
{isThinking && message.content.length <= 1 ? ( - Thinking... + {t("thinking")} ) : ( - Streaming + {t("streaming")} )} | - {formatElapsed(elapsed)} + {elapsedLabel} {editStats.files > 0 && ( <> | - {editStats.files}F +{formatCompactInt(editStats.additions)}/- - {formatCompactInt(editStats.deletions)} + {editStats.files}F + + {formatCompactInt(editStats.additions, compactNumberFormatter)}/- + {formatCompactInt(editStats.deletions, compactNumberFormatter)} )} @@ -323,7 +330,7 @@ export function LiveTurnStats({ message }: LiveTurnStatsProps) { | - {toolCallCount} tool {toolCallCount === 1 ? "use" : "uses"} + {t("toolUseCount", { count: toolCallCount })} )} diff --git a/src/components/message/message-list-view.tsx b/src/components/message/message-list-view.tsx index f0dc4ef..aa6b99c 100644 --- a/src/components/message/message-list-view.tsx +++ b/src/components/message/message-list-view.tsx @@ -25,6 +25,7 @@ import { } from "@/components/ai-elements/message-thread" import { Message, MessageContent } from "@/components/ai-elements/message" import { Loader2 } from "lucide-react" +import { useTranslations } from "next-intl" import { buildPlanKey, extractLatestPlanEntriesFromMessages, @@ -46,7 +47,10 @@ interface ResolvedMessageGroup extends MessageGroup { resources: UserResourceDisplay[] } -function fallbackExtractUserResources(group: MessageGroup): { +function fallbackExtractUserResources( + group: MessageGroup, + attachedResourcesText: string +): { parts: AdaptedContentPart[] resources: UserResourceDisplay[] } { @@ -87,14 +91,17 @@ function fallbackExtractUserResources(group: MessageGroup): { } if (parsedParts.length === 0 && dedupedResources.length > 0) { - parsedParts.push({ type: "text", text: "Attached resources" }) + parsedParts.push({ type: "text", text: attachedResourcesText }) } return { parts: parsedParts, resources: dedupedResources } } -function resolveMessageGroup(group: MessageGroup): ResolvedMessageGroup { - const resolved = fallbackExtractUserResources(group) +function resolveMessageGroup( + group: MessageGroup, + attachedResourcesText: string +): ResolvedMessageGroup { + const resolved = fallbackExtractUserResources(group, attachedResourcesText) return { ...group, parts: resolved.parts, @@ -161,6 +168,7 @@ export function MessageListView({ onPendingClear, isActive = true, }: MessageListViewProps) { + const t = useTranslations("Folder.chat.messageList") const { detail, loading, error, refetch } = useDbMessageDetail(conversationId) const turnCount = detail?.turns.length ?? 0 @@ -225,12 +233,16 @@ export function MessageListView({ [pendingMessages] ) const resolvedGroups = useMemo( - () => groups.map(resolveMessageGroup), - [groups] + () => + groups.map((group) => resolveMessageGroup(group, t("attachedResources"))), + [groups, t] ) const resolvedPendingGroups = useMemo( - () => pendingGroups.map(resolveMessageGroup), - [pendingGroups] + () => + pendingGroups.map((group) => + resolveMessageGroup(group, t("attachedResources")) + ), + [pendingGroups, t] ) const showLiveMessage = Boolean( @@ -245,7 +257,7 @@ export function MessageListView({
- Loading... + {t("loading")}
) @@ -255,7 +267,9 @@ export function MessageListView({ return (
-

Error: {error}

+

+ {t("error", { message: error })} +

) @@ -276,7 +290,7 @@ export function MessageListView({ !showLiveMessage ? (

- No messages in this conversation. + {t("emptyConversation")}

) : ( diff --git a/src/contexts/workspace-context.tsx b/src/contexts/workspace-context.tsx index b41664e..617b4ed 100644 --- a/src/contexts/workspace-context.tsx +++ b/src/contexts/workspace-context.tsx @@ -10,6 +10,7 @@ import { useState, type ReactNode, } from "react" +import { useTranslations } from "next-intl" import { useFolderContext } from "@/contexts/folder-context" import { gitDiff, @@ -172,6 +173,7 @@ interface WorkspaceProviderProps { } export function WorkspaceProvider({ children }: WorkspaceProviderProps) { + const t = useTranslations("Folder.workspaceContext") const { folder, folderId } = useFolderContext() const folderPath = folder?.path const storageKey = useMemo( @@ -270,7 +272,11 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const rejectTab = useCallback( (tabId: string, errorMessage: string) => { - resolveTab(tabId, `Unable to load content.\n\n${errorMessage}`, false) + resolveTab( + tabId, + t("unableLoadContent", { message: errorMessage }), + false + ) setFileTabs((prev) => prev.map((tab) => tab.id === tabId @@ -283,7 +289,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { ) ) }, - [resolveTab] + [resolveTab, t] ) const resolveRichDiffTab = useCallback( @@ -333,7 +339,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { })(), ]), 15_000, - "Preview request timed out" + t("previewRequestTimedOut") ) setFileTabs((prev) => prev.map((tab) => @@ -360,7 +366,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { rejectTab(tabId, error instanceof Error ? error.message : String(error)) } }, - [folderPath, rejectTab, upsertLoadingTab] + [folderPath, rejectTab, t, upsertLoadingTab] ) const openWorkingTreeDiff = useCallback( @@ -372,8 +378,8 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { if (!rawPath) { const tabId = "diff:working:all" - const title = "Diff · Workspace" - const description = "Working tree (HEAD)" + const title = t("diffTitleWorkspace") + const description = t("diffDescriptionWorkingTree") upsertLoadingTab( loadingTab(tabId, "diff", title, description, null, "diff") ) @@ -381,9 +387,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const result = await withTimeout( gitDiff(folderPath), 20_000, - "Diff request timed out" + t("diffRequestTimedOut") ) - resolveTab(tabId, result || "No changes.", false) + resolveTab(tabId, result || t("noChanges"), false) } catch (error) { rejectTab( tabId, @@ -399,7 +405,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { if (mode === "overview") { const encodedPath = encodeURIComponent(path) const tabId = `diff:working-overview:${encodedPath}` - const title = `Diff · ${fileName(path)}` + const title = t("diffTitleFile", { name: fileName(path) }) const description = path upsertLoadingTab( loadingTab(tabId, "diff", title, description, path, "diff") @@ -409,9 +415,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const result = await withTimeout( gitDiff(folderPath, path), 20_000, - "Diff request timed out" + t("diffRequestTimedOut") ) - resolveTab(tabId, result || "No changes.", false) + resolveTab(tabId, result || t("noChanges"), false) } catch (error) { rejectTab( tabId, @@ -423,7 +429,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { if (mode === "unified") { const tabId = `diff:working:${path}:unified` - const title = `Diff · ${fileName(path)}` + const title = t("diffTitleFile", { name: fileName(path) }) const description = path upsertLoadingTab( loadingTab(tabId, "diff", title, description, path, "diff") @@ -433,9 +439,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const result = await withTimeout( gitDiff(folderPath, path), 20_000, - "Diff request timed out" + t("diffRequestTimedOut") ) - resolveTab(tabId, result || "No changes.", false) + resolveTab(tabId, result || t("noChanges"), false) } catch (error) { rejectTab( tabId, @@ -446,7 +452,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { } const tabId = `diff:working:${path}` - const title = `Diff · ${fileName(path)}` + const title = t("diffTitleFile", { name: fileName(path) }) const description = path const lang = languageFromPath(path) @@ -465,14 +471,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { })), ]), 20_000, - "Diff request timed out" + t("diffRequestTimedOut") ) resolveRichDiffTab(tabId, originalContent, modifiedResult.content) } catch (error) { rejectTab(tabId, error instanceof Error ? error.message : String(error)) } }, - [folderPath, rejectTab, resolveTab, resolveRichDiffTab, upsertLoadingTab] + [folderPath, rejectTab, resolveTab, resolveRichDiffTab, t, upsertLoadingTab] ) const openBranchDiff = useCallback( @@ -494,11 +500,11 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { ? `diff:branch-overview:${encodedBranch}:${encodedPath}` : `diff:branch:${targetBranch}:${path ?? "all"}` const title = path - ? `Compare · ${fileName(path)}` - : `Compare · ${targetBranch}` + ? t("compareTitleFile", { name: fileName(path) }) + : t("compareTitleBranch", { branch: targetBranch }) const description = path - ? `${path} · compare with ${targetBranch}` - : `compare with ${targetBranch}` + ? t("compareDescriptionPath", { path, branch: targetBranch }) + : t("compareDescriptionBranch", { branch: targetBranch }) if (mode !== "overview" && path) { const lang = languageFromPath(path) @@ -517,7 +523,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { })), ]), 20_000, - "Branch compare request timed out" + t("branchCompareRequestTimedOut") ) resolveRichDiffTab(tabId, originalContent, modifiedResult.content) } catch (error) { @@ -537,14 +543,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const result = await withTimeout( gitDiffWithBranch(folderPath, targetBranch, path ?? undefined), 20_000, - "Branch compare request timed out" + t("branchCompareRequestTimedOut") ) - resolveTab(tabId, result || "No changes.", false) + resolveTab(tabId, result || t("noChanges"), false) } catch (error) { rejectTab(tabId, error instanceof Error ? error.message : String(error)) } }, - [folderPath, rejectTab, resolveRichDiffTab, resolveTab, upsertLoadingTab] + [folderPath, rejectTab, resolveRichDiffTab, resolveTab, t, upsertLoadingTab] ) const openCommitDiff = useCallback( @@ -553,11 +559,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const path = rawPath ? normalizePath(rawPath) : null const tabId = `diff:commit:${commit}:${path ?? "all"}` const title = path - ? `Diff · ${fileName(path)} @ ${commit.slice(0, 7)}` - : `Diff · ${commit.slice(0, 7)}` + ? t("diffTitleCommitFile", { + name: fileName(path), + hash: commit.slice(0, 7), + }) + : t("diffTitleCommit", { hash: commit.slice(0, 7) }) const description = path - ? `${path} · commit ${commit}` - : message || `commit ${commit}` + ? t("diffDescriptionCommitPath", { path, commit }) + : message || t("diffDescriptionCommit", { commit }) if (path) { const lang = languageFromPath(path) @@ -572,7 +581,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { gitShowFile(folderPath, path, commit).catch(() => ""), ]), 20_000, - "Commit diff request timed out" + t("commitDiffRequestTimedOut") ) resolveRichDiffTab(tabId, originalContent, modifiedContent) } catch (error) { @@ -590,9 +599,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const result = await withTimeout( gitShowDiff(folderPath, commit, undefined), 20_000, - "Commit diff request timed out" + t("commitDiffRequestTimedOut") ) - resolveTab(tabId, result || "No diff output.", false) + resolveTab(tabId, result || t("noDiffOutput"), false) } catch (error) { rejectTab( tabId, @@ -601,14 +610,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { } } }, - [folderPath, rejectTab, resolveTab, resolveRichDiffTab, upsertLoadingTab] + [folderPath, rejectTab, resolveTab, resolveRichDiffTab, t, upsertLoadingTab] ) const openSessionFileDiff = useCallback( (filePath: string, diffContent: string, groupLabel: string) => { const path = normalizePath(filePath) const tabId = `diff:session:${groupLabel}:${path}` - const title = `Diff · ${fileName(path)}` + const title = t("diffTitleFile", { name: fileName(path) }) const description = `${path} · ${groupLabel}` const tab: FileWorkspaceTab = { @@ -624,15 +633,15 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { upsertLoadingTab(tab) }, - [upsertLoadingTab] + [t, upsertLoadingTab] ) const openExternalConflictDiff = useCallback( (filePath: string, diskContent: string, unsavedContent: string) => { const path = normalizePath(filePath) const tabId = `diff:external-conflict:${path}` - const title = `Conflict · ${fileName(path)}` - const description = `${path} · disk vs unsaved` + const title = t("diffTitleConflictFile", { name: fileName(path) }) + const description = t("diffDescriptionConflict", { path }) const language = languageFromPath(path) const tab: FileWorkspaceTab = { @@ -650,7 +659,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { upsertLoadingTab(tab) }, - [upsertLoadingTab] + [t, upsertLoadingTab] ) const updateActiveFileContent = useCallback( @@ -712,7 +721,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { expectedEtag ), 20_000, - "Save request timed out" + t("saveRequestTimedOut") ) setFileTabs((prev) => @@ -753,7 +762,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { return false } }, - [folderPath] + [folderPath, t] ) const saveActiveFile = useCallback( @@ -799,7 +808,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { })(), ]), 15_000, - "Reload request timed out" + t("reloadRequestTimedOut") ) setFileTabs((prev) => prev.map((candidate) => @@ -838,7 +847,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { ) } }, - [folderPath] + [folderPath, t] ) const reloadActiveFile = useCallback(async () => { @@ -866,7 +875,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const tab = prev[idx] if (isDirtyFileTab(tab)) { const confirmed = window.confirm( - `“${tab.title}” 有未保存更改,确定关闭吗?` + t("confirmCloseDirtyTab", { title: tab.title }) ) if (!confirmed) return prev } @@ -886,7 +895,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { return next }) }, - [activateConversationPane] + [activateConversationPane, t] ) const closeOtherFileTabs = useCallback( @@ -897,9 +906,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { const closingTabs = prev.filter((tab) => tab.id !== tabId) if (closingTabs.some(isDirtyFileTab)) { - const confirmed = window.confirm( - "存在未保存文件,确定关闭其它标签页吗?" - ) + const confirmed = window.confirm(t("confirmCloseOtherDirtyTabs")) if (!confirmed) return prev } @@ -908,15 +915,13 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { return remaining }) }, - [activateFilePane] + [activateFilePane, t] ) const closeAllFileTabs = useCallback(() => { setFileTabs((prev) => { if (prev.some(isDirtyFileTab)) { - const confirmed = window.confirm( - "存在未保存文件,确定关闭全部标签页吗?" - ) + const confirmed = window.confirm(t("confirmCloseAllDirtyTabs")) if (!confirmed) return prev } @@ -924,7 +929,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) { activateConversationPane() return [] }) - }, [activateConversationPane]) + }, [activateConversationPane, t]) const reorderFileTabs = useCallback((tabs: FileWorkspaceTab[]) => { setFileTabs(tabs) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index e0461ad..d1a641a 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -513,6 +513,11 @@ "SettingsPages": { "agentsLoading": "Loading agent settings..." }, + "CommitPage": { + "title": "Commit", + "invalidFolderId": "Invalid folder ID", + "loadingRepo": "Loading repository..." + }, "Folder": { "common": { "all": "All", @@ -1023,6 +1028,33 @@ "saving": "Saving..." } }, + "workspaceContext": { + "confirmCloseDirtyTab": "Close \"{title}\" without saving?", + "confirmCloseOtherDirtyTabs": "Close other tabs with unsaved changes?", + "confirmCloseAllDirtyTabs": "Close all tabs with unsaved changes?", + "unableLoadContent": "Unable to load content.\n\n{message}", + "previewRequestTimedOut": "Preview request timed out", + "diffRequestTimedOut": "Diff request timed out", + "branchCompareRequestTimedOut": "Branch compare request timed out", + "commitDiffRequestTimedOut": "Commit diff request timed out", + "saveRequestTimedOut": "Save request timed out", + "reloadRequestTimedOut": "Reload request timed out", + "noChanges": "No changes.", + "noDiffOutput": "No diff output.", + "diffTitleWorkspace": "Diff · Workspace", + "diffDescriptionWorkingTree": "Working tree (HEAD)", + "diffTitleFile": "Diff · {name}", + "compareTitleFile": "Compare · {name}", + "compareTitleBranch": "Compare · {branch}", + "compareDescriptionPath": "{path} · compare with {branch}", + "compareDescriptionBranch": "compare with {branch}", + "diffTitleCommitFile": "Diff · {name} @ {hash}", + "diffTitleCommit": "Diff · {hash}", + "diffDescriptionCommitPath": "{path} · commit {commit}", + "diffDescriptionCommit": "commit {commit}", + "diffTitleConflictFile": "Conflict · {name}", + "diffDescriptionConflict": "{path} · disk vs unsaved" + }, "chat": { "chatInput": { "connecting": "Connecting...", @@ -1078,6 +1110,49 @@ "moreFiles": "+{count} more files", "plan": "Plan", "targetMode": "Target mode: {mode}" + }, + "messageBranch": { + "previousBranchAria": "Previous branch", + "nextBranchAria": "Next branch", + "pageOf": "{current} of {total}" + }, + "terminal": { + "title": "Terminal", + "running": "Running" + }, + "reasoning": { + "thinking": "Thinking...", + "thoughtForFewSeconds": "Thought for a few seconds", + "thoughtForSeconds": "Thought for {duration} seconds" + }, + "messageList": { + "attachedResources": "Attached resources", + "loading": "Loading...", + "error": "Error: {message}", + "emptyConversation": "No messages in this conversation." + }, + "liveTurnStats": { + "thinking": "Thinking...", + "streaming": "Streaming", + "elapsedMinutes": "{value}m", + "elapsedSeconds": "{value}s", + "toolUseCount": "{count} tool {count, plural, one {use} other {uses}}" + }, + "tool": { + "parameters": "Parameters", + "status": { + "approvalRequested": "Awaiting Approval", + "approvalResponded": "Responded", + "inputAvailable": "Running", + "inputStreaming": "Pending", + "outputAvailable": "Completed", + "outputDenied": "Denied", + "outputError": "Error" + } + }, + "contentParts": { + "showingTailOutput": "Showing tail output while streaming for performance.", + "result": "Result" } }, "diffPreview": { diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index f6ff650..640c695 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -513,6 +513,11 @@ "SettingsPages": { "agentsLoading": "加载 Agent 设置中..." }, + "CommitPage": { + "title": "提交代码", + "invalidFolderId": "无效的 folderId", + "loadingRepo": "正在加载仓库..." + }, "Folder": { "common": { "all": "全部", @@ -1023,6 +1028,33 @@ "saving": "保存中..." } }, + "workspaceContext": { + "confirmCloseDirtyTab": "文件“{title}”有未保存更改,确定关闭吗?", + "confirmCloseOtherDirtyTabs": "其它标签页有未保存更改,确定关闭吗?", + "confirmCloseAllDirtyTabs": "存在未保存更改,确定关闭全部标签页吗?", + "unableLoadContent": "无法加载内容。\n\n{message}", + "previewRequestTimedOut": "预览请求超时", + "diffRequestTimedOut": "Diff 请求超时", + "branchCompareRequestTimedOut": "分支比较请求超时", + "commitDiffRequestTimedOut": "提交差异请求超时", + "saveRequestTimedOut": "保存请求超时", + "reloadRequestTimedOut": "重载请求超时", + "noChanges": "暂无变更。", + "noDiffOutput": "无差异输出。", + "diffTitleWorkspace": "Diff · 工作区", + "diffDescriptionWorkingTree": "工作区变更(HEAD)", + "diffTitleFile": "Diff · {name}", + "compareTitleFile": "比较 · {name}", + "compareTitleBranch": "比较 · {branch}", + "compareDescriptionPath": "{path} · 与 {branch} 比较", + "compareDescriptionBranch": "与 {branch} 比较", + "diffTitleCommitFile": "Diff · {name} @ {hash}", + "diffTitleCommit": "Diff · {hash}", + "diffDescriptionCommitPath": "{path} · 提交 {commit}", + "diffDescriptionCommit": "提交 {commit}", + "diffTitleConflictFile": "冲突 · {name}", + "diffDescriptionConflict": "{path} · 磁盘与未保存内容" + }, "chat": { "chatInput": { "connecting": "连接中...", @@ -1078,6 +1110,49 @@ "moreFiles": "+{count} 个更多文件", "plan": "计划", "targetMode": "目标模式:{mode}" + }, + "messageBranch": { + "previousBranchAria": "上一分支", + "nextBranchAria": "下一分支", + "pageOf": "{current} / {total}" + }, + "terminal": { + "title": "终端", + "running": "运行中" + }, + "reasoning": { + "thinking": "思考中...", + "thoughtForFewSeconds": "思考了几秒", + "thoughtForSeconds": "思考了 {duration} 秒" + }, + "messageList": { + "attachedResources": "附加资源", + "loading": "加载中...", + "error": "错误:{message}", + "emptyConversation": "当前会话暂无消息。" + }, + "liveTurnStats": { + "thinking": "思考中...", + "streaming": "生成中", + "elapsedMinutes": "{value} 分钟", + "elapsedSeconds": "{value} 秒", + "toolUseCount": "{count} 次工具调用" + }, + "tool": { + "parameters": "参数", + "status": { + "approvalRequested": "等待授权", + "approvalResponded": "已响应", + "inputAvailable": "运行中", + "inputStreaming": "等待中", + "outputAvailable": "已完成", + "outputDenied": "已拒绝", + "outputError": "错误" + } + }, + "contentParts": { + "showingTailOutput": "为保证性能,流式输出时仅显示尾部内容。", + "result": "结果" } }, "diffPreview": { diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index e33251f..281b78c 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -513,6 +513,11 @@ "SettingsPages": { "agentsLoading": "載入 Agent 設定中..." }, + "CommitPage": { + "title": "提交程式碼", + "invalidFolderId": "無效的 folderId", + "loadingRepo": "正在載入倉庫..." + }, "Folder": { "common": { "all": "全部", @@ -1023,6 +1028,33 @@ "saving": "儲存中..." } }, + "workspaceContext": { + "confirmCloseDirtyTab": "檔案「{title}」有未儲存變更,確定關閉嗎?", + "confirmCloseOtherDirtyTabs": "其他分頁有未儲存變更,確定關閉嗎?", + "confirmCloseAllDirtyTabs": "存在未儲存變更,確定關閉全部分頁嗎?", + "unableLoadContent": "無法載入內容。\n\n{message}", + "previewRequestTimedOut": "預覽請求逾時", + "diffRequestTimedOut": "Diff 請求逾時", + "branchCompareRequestTimedOut": "分支比較請求逾時", + "commitDiffRequestTimedOut": "提交差異請求逾時", + "saveRequestTimedOut": "儲存請求逾時", + "reloadRequestTimedOut": "重載請求逾時", + "noChanges": "暫無變更。", + "noDiffOutput": "無差異輸出。", + "diffTitleWorkspace": "Diff · 工作區", + "diffDescriptionWorkingTree": "工作區變更(HEAD)", + "diffTitleFile": "Diff · {name}", + "compareTitleFile": "比較 · {name}", + "compareTitleBranch": "比較 · {branch}", + "compareDescriptionPath": "{path} · 與 {branch} 比較", + "compareDescriptionBranch": "與 {branch} 比較", + "diffTitleCommitFile": "Diff · {name} @ {hash}", + "diffTitleCommit": "Diff · {hash}", + "diffDescriptionCommitPath": "{path} · 提交 {commit}", + "diffDescriptionCommit": "提交 {commit}", + "diffTitleConflictFile": "衝突 · {name}", + "diffDescriptionConflict": "{path} · 磁碟與未儲存內容" + }, "chat": { "chatInput": { "connecting": "連線中...", @@ -1078,6 +1110,49 @@ "moreFiles": "+{count} 個更多檔案", "plan": "計畫", "targetMode": "目標模式:{mode}" + }, + "messageBranch": { + "previousBranchAria": "上一個分支", + "nextBranchAria": "下一個分支", + "pageOf": "{current} / {total}" + }, + "terminal": { + "title": "終端", + "running": "執行中" + }, + "reasoning": { + "thinking": "思考中...", + "thoughtForFewSeconds": "思考了幾秒", + "thoughtForSeconds": "思考了 {duration} 秒" + }, + "messageList": { + "attachedResources": "附加資源", + "loading": "載入中...", + "error": "錯誤:{message}", + "emptyConversation": "目前會話暫無訊息。" + }, + "liveTurnStats": { + "thinking": "思考中...", + "streaming": "生成中", + "elapsedMinutes": "{value} 分鐘", + "elapsedSeconds": "{value} 秒", + "toolUseCount": "{count} 次工具呼叫" + }, + "tool": { + "parameters": "參數", + "status": { + "approvalRequested": "等待授權", + "approvalResponded": "已回應", + "inputAvailable": "執行中", + "inputStreaming": "等待中", + "outputAvailable": "已完成", + "outputDenied": "已拒絕", + "outputError": "錯誤" + } + }, + "contentParts": { + "showingTailOutput": "為確保效能,串流輸出時僅顯示尾端內容。", + "result": "結果" } }, "diffPreview": {