From a356b813a6d45d5e85dd1ee27b0e8b5281a0e3f0 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Sat, 7 Mar 2026 13:44:40 +0800 Subject: [PATCH] =?UTF-8?q?folder=E9=A1=B5=E9=9D=A2=E5=85=A8=E9=87=8F?= =?UTF-8?q?=E5=A4=9A=E8=AF=AD=E8=A8=80=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/aux-panel-file-tree-tab.tsx | 291 +++++++++------- .../layout/aux-panel-git-changes-tab.tsx | 152 ++++++--- .../layout/aux-panel-git-log-tab.tsx | 147 ++++++--- src/components/layout/branch-dropdown.tsx | 176 ++++++---- src/components/layout/command-dropdown.tsx | 12 +- .../layout/command-manage-dialog.tsx | 17 +- src/components/layout/commit-dialog.tsx | 148 ++++++--- src/i18n/messages/en.json | 311 ++++++++++++++++++ src/i18n/messages/zh-CN.json | 311 ++++++++++++++++++ src/i18n/messages/zh-TW.json | 311 ++++++++++++++++++ 10 files changed, 1533 insertions(+), 343 deletions(-) diff --git a/src/components/layout/aux-panel-file-tree-tab.tsx b/src/components/layout/aux-panel-file-tree-tab.tsx index 682cb16..2d6271e 100644 --- a/src/components/layout/aux-panel-file-tree-tab.tsx +++ b/src/components/layout/aux-panel-file-tree-tab.tsx @@ -12,6 +12,7 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event" import { revealItemInDir } from "@tauri-apps/plugin-opener" import ignore from "ignore" import { Check, ChevronRight } from "lucide-react" +import { useTranslations } from "next-intl" import { toast } from "sonner" import { useFolderContext } from "@/contexts/folder-context" import { useAuxPanelContext } from "@/contexts/aux-panel-context" @@ -107,14 +108,6 @@ function baseName(path: string): string { const FILE_TREE_ROOT_PATH = "__workspace_root__" const GITIGNORE_MUTED_CLASS = "text-muted-foreground/55" -function getSystemExplorerLabel(): string { - if (typeof navigator === "undefined") return "在文件管理器打开" - const platform = `${navigator.platform} ${navigator.userAgent}`.toLowerCase() - if (platform.includes("mac")) return "在访达打开" - if (platform.includes("win")) return "在资源管理器打开" - return "在文件管理器打开" -} - interface FileActionTarget { kind: "file" | "dir" path: string @@ -447,14 +440,26 @@ function RenderNode({ onRequestDelete, onRefresh, }: RenderNodeProps) { + const t = useTranslations("Folder.fileTreeTab") + const tCommon = useTranslations("Folder.common") const isGitignoreIgnored = ancestorGitignoreIgnored || gitignoreIgnoredPaths.has(node.path) + const systemExplorerLabel = + typeof navigator === "undefined" + ? t("openInFileManager") + : (() => { + const platform = + `${navigator.platform} ${navigator.userAgent}`.toLowerCase() + if (platform.includes("mac")) return t("openInFinder") + if (platform.includes("win")) return t("openInExplorer") + return t("openInFileManager") + })() + if (node.kind === "file") { const gitStatusCode = gitStatusByPath.get(node.path) const absolutePath = joinFsPath(workspacePath, node.path) const dirPath = parentDir(absolutePath) - const systemExplorerLabel = getSystemExplorerLabel() const isGitMenuDisabled = !gitEnabled || isGitignoreIgnored const handleAttachToSession = () => { @@ -470,7 +475,7 @@ function RenderNode({ await revealItemInDir(absolutePath) } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("打开目录失败", { description: message }) + toast.error(t("toasts.openDirectoryFailed"), { description: message }) } } @@ -489,17 +494,17 @@ function RenderNode({ onOpenFilePreview(node.path)}> - 打开文件 + {tCommon("openFile")} void handleAttachToSession()} disabled={!activeSessionTabId} > - 附加到当前会话 + {t("attachToCurrentSession")} - Git + {t("git")} - 添加到VCS + {t("actions.addToVcs")} onOpenFileDiff(node.path)} disabled={isGitMenuDisabled} > - 查看差异 + {tCommon("viewDiff")} onRequestCompareWithBranch(node)} disabled={isGitMenuDisabled} > - 与分支比较... + {t("compareWithBranch")} onRequestRollback(node)} disabled={isGitMenuDisabled} > - 回滚 + {t("actions.rollback")} onRequestRename(node)}> - 重命名 + {tCommon("rename")} + + + {t("reloadFromDisk")} - 从磁盘重新加载 - 打开于 + {t("openIn")} void handleOpenInSystemExplorer()} @@ -547,7 +554,7 @@ function RenderNode({ void onOpenDirInTerminal(dirPath, node.name)} > - 在终端打开 + {t("openInTerminal")} @@ -555,7 +562,7 @@ function RenderNode({ onSelect={() => onRequestDelete(node)} variant="destructive" > - 删除 + {tCommon("delete")} @@ -563,7 +570,6 @@ function RenderNode({ } const absolutePath = joinFsPath(workspacePath, node.path) - const systemExplorerLabel = getSystemExplorerLabel() const dirHasChanges = !isGitignoreIgnored && gitChangedDirPaths.has(node.path) const isGitMenuDisabled = !gitEnabled || isGitignoreIgnored const shouldRenderChildren = expandedPaths.has(node.path) @@ -573,7 +579,7 @@ function RenderNode({ await revealItemInDir(absolutePath) } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("打开目录失败", { description: message }) + toast.error(t("toasts.openDirectoryFailed"), { description: message }) } } @@ -623,41 +629,41 @@ function RenderNode({ - Git + {t("git")} onRequestAddToVcs(node)} disabled={isGitMenuDisabled} > - 添加到VCS + {t("actions.addToVcs")} onOpenDirDiff(node.path)} disabled={isGitMenuDisabled} > - 查看差异 + {tCommon("viewDiff")} onRequestCompareWithBranch(node)} disabled={isGitMenuDisabled} > - 与分支比较... + {t("compareWithBranch")} onRequestRollback(node)} disabled={isGitMenuDisabled} > - 回滚 + {t("actions.rollback")} onRequestRename(node)}> - 重命名 + {tCommon("rename")} - 打开于 + {t("openIn")} void handleOpenDirInSystemExplorer()} @@ -667,16 +673,18 @@ function RenderNode({ void onOpenDirInTerminal(absolutePath, node.name)} > - 在终端打开 + {t("openInTerminal")} - 从磁盘重新加载 + + {t("reloadFromDisk")} + onRequestDelete(node)} variant="destructive" > - 删除 + {tCommon("delete")} @@ -684,6 +692,8 @@ function RenderNode({ } export function FileTreeTab() { + const t = useTranslations("Folder.fileTreeTab") + const tCommon = useTranslations("Folder.common") const { activeTab } = useAuxPanelContext() const { folder } = useFolderContext() const { tabs, activeTabId } = useTabContext() @@ -1095,13 +1105,13 @@ export function FileTreeTab() { const handleOpenDirInTerminal = useCallback( async (dirPath: string, fileName: string) => { - const terminalTitle = `Terminal · ${baseName(fileName)}` + const terminalTitle = t("terminalTitle", { name: baseName(fileName) }) const terminalId = await createTerminalInDirectory(dirPath, terminalTitle) if (!terminalId) { - toast.error("无法打开内置终端") + toast.error(t("toasts.openBuiltinTerminalFailed")) } }, - [createTerminalInDirectory] + [createTerminalInDirectory, t] ) const handleRequestRename = useCallback((target: FileActionTarget) => { @@ -1146,8 +1156,8 @@ export function FileTreeTab() { resetDirectoryGitActionDialog() toast.info( action === "add" - ? "该目录下没有可添加到VCS的变更文件" - : "该目录下没有可回滚的变更文件" + ? t("toasts.noAddableFilesInDir") + : t("toasts.noRollbackFilesInDir") ) return } @@ -1168,7 +1178,7 @@ export function FileTreeTab() { setDirectoryGitLoading(false) } }, - [folder?.path, resetDirectoryGitActionDialog] + [folder?.path, resetDirectoryGitActionDialog, t] ) const handleRequestRollback = useCallback( @@ -1191,14 +1201,14 @@ export function FileTreeTab() { if (!folder?.path) return try { await gitAddFiles(folder.path, [target.path]) - toast.success(`已添加 ${target.name} 到VCS`) + toast.success(t("toasts.addedToVcs", { name: target.name })) await fetchTree() } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("添加到VCS失败", { description: message }) + toast.error(t("toasts.addToVcsFailed"), { description: message }) } }, - [fetchTree, folder?.path, openDirectoryGitActionDialog] + [fetchTree, folder?.path, openDirectoryGitActionDialog, t] ) const loadCompareBranches = useCallback(async () => { @@ -1222,7 +1232,7 @@ export function FileTreeTab() { branchesResult.reason instanceof Error ? branchesResult.reason.message : String(branchesResult.reason) - toast.error("加载分支失败", { description: message }) + toast.error(t("toasts.loadBranchesFailed"), { description: message }) } if (currentBranchResult.status === "fulfilled") { @@ -1234,11 +1244,11 @@ export function FileTreeTab() { setCompareBranchList({ local: [], remote: [], worktree_branches: [] }) setCompareCurrentBranch(null) const message = error instanceof Error ? error.message : String(error) - toast.error("加载分支失败", { description: message }) + toast.error(t("toasts.loadBranchesFailed"), { description: message }) } finally { setCompareBranchLoading(false) } - }, [folder?.path]) + }, [folder?.path, t]) const handleRequestCompareWithBranch = useCallback( (target: FileActionTarget) => { @@ -1433,7 +1443,10 @@ export function FileTreeTab() { ? "flex h-4 w-4 shrink-0 items-center justify-center rounded border border-primary bg-primary text-primary-foreground transition-colors" : "flex h-4 w-4 shrink-0 items-center justify-center rounded border border-input transition-colors" } - aria-label={`${selected ? "取消选择" : "选择"} ${node.path}`} + aria-label={t("aria.selectPath", { + action: selected ? t("actions.unselect") : t("actions.select"), + path: node.path, + })} disabled={directoryGitSubmitting} > {selected && } @@ -1461,6 +1474,7 @@ export function FileTreeTab() { directoryGitSelectedPaths, directoryGitSubmitting, handleToggleDirectoryGitFile, + t, ] ) @@ -1480,11 +1494,11 @@ export function FileTreeTab() { await fetchTree() } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("重命名失败", { description: message }) + toast.error(t("toasts.renameFailed"), { description: message }) } finally { setRenaming(false) } - }, [fetchTree, folder?.path, renameTarget, renameValue]) + }, [fetchTree, folder?.path, renameTarget, renameValue, t]) const handleDeleteConfirm = useCallback(async () => { if (!folder?.path || !deleteTarget) return @@ -1495,27 +1509,27 @@ export function FileTreeTab() { await fetchTree() } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("删除失败", { description: message }) + toast.error(t("toasts.deleteFailed"), { description: message }) } finally { setDeleting(false) } - }, [deleteTarget, fetchTree, folder?.path]) + }, [deleteTarget, fetchTree, folder?.path, t]) const handleRollbackConfirm = useCallback(async () => { if (!folder?.path || !rollbackTarget) return setRollingBack(true) try { await gitRollbackFile(folder.path, rollbackTarget.path) - toast.success(`已回滚 ${rollbackTarget.name}`) + toast.success(t("toasts.rolledBack", { name: rollbackTarget.name })) setRollbackTarget(null) await fetchTree() } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("回滚失败", { description: message }) + toast.error(t("toasts.rollbackFailed"), { description: message }) } finally { setRollingBack(false) } - }, [fetchTree, folder?.path, rollbackTarget]) + }, [fetchTree, folder?.path, rollbackTarget, t]) const handleDirectoryGitActionConfirm = useCallback(async () => { if (!folder?.path || !directoryGitActionType) return @@ -1528,12 +1542,20 @@ export function FileTreeTab() { try { if (directoryGitActionType === "add") { await gitAddFiles(folder.path, selectedPaths) - toast.success(`已添加 ${selectedPaths.length} 个文件到VCS`) + toast.success( + t("toasts.addedFilesToVcs", { + count: selectedPaths.length, + }) + ) } else { for (const filePath of selectedPaths) { await gitRollbackFile(folder.path, filePath) } - toast.success(`已回滚 ${selectedPaths.length} 个文件`) + toast.success( + t("toasts.rolledBackFiles", { + count: selectedPaths.length, + }) + ) } resetDirectoryGitActionDialog() @@ -1542,7 +1564,9 @@ export function FileTreeTab() { const message = error instanceof Error ? error.message : String(error) setDirectoryGitError(message) toast.error( - directoryGitActionType === "add" ? "添加到VCS失败" : "回滚失败", + directoryGitActionType === "add" + ? t("toasts.addToVcsFailed") + : t("toasts.rollbackFailed"), { description: message, } @@ -1556,6 +1580,7 @@ export function FileTreeTab() { fetchTree, folder?.path, resetDirectoryGitActionDialog, + t, ]) const handleCompareBranchClick = useCallback( @@ -1633,23 +1658,23 @@ export function FileTreeTab() { externalConflictPrompt.path, unsavedContent ) - toast.success("已另存为副本", { + toast.success(t("toasts.savedAsCopy"), { description: result.path, }) setExternalConflictPrompt(null) void fetchTree({ silent: true }) } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("另存为副本失败", { description: message }) + toast.error(t("toasts.saveCopyFailed"), { description: message }) } finally { setSavingExternalConflictCopy(false) } - }, [externalConflictPrompt, fetchTree, folder?.path]) + }, [externalConflictPrompt, fetchTree, folder?.path, t]) const rootNodeName = useMemo(() => { - if (!folder?.path) return "Workspace" + if (!folder?.path) return t("workspace") return baseName(folder.path) - }, [folder?.path]) + }, [folder?.path, t]) useEffect(() => { if (!isFileTreeTabActive) return @@ -1807,7 +1832,7 @@ export function FileTreeTab() { await startFileTreeWatch(rootPath) } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("文件监听启动失败", { description: message }) + toast.error(t("toasts.watchStartFailed"), { description: message }) } try { @@ -1915,7 +1940,7 @@ export function FileTreeTab() { } void stopFileTreeWatch(rootPath) } - }, [fetchTree, folder?.path, openFilePreview]) + }, [fetchTree, folder?.path, openFilePreview, t]) if (loading && nodes.length === 0) { return ( @@ -1941,7 +1966,7 @@ export function FileTreeTab() { void fetchTree() }} > - Retry + {t("retry")} ) @@ -2009,7 +2034,7 @@ export function FileTreeTab() { void fetchTree() }} > - 从磁盘重新加载 + {t("reloadFromDisk")} @@ -2025,10 +2050,12 @@ export function FileTreeTab() { - {renameTarget?.kind === "dir" ? "重命名目录" : "重命名文件"} + {renameTarget?.kind === "dir" + ? t("renameDialog.renameDirectory") + : t("renameDialog.renameFile")} - 输入新的名称(仅名称,不含路径)。 + {t("renameDialog.description")}
@@ -2059,10 +2086,10 @@ export function FileTreeTab() { setRenameValue("") }} > - 取消 + {tCommon("cancel")}
@@ -2079,19 +2106,29 @@ export function FileTreeTab() { - {directoryGitActionType === "add" ? "添加到VCS" : "回滚"} + {directoryGitActionType === "add" + ? t("actions.addToVcs") + : t("actions.rollback")} {directoryGitActionTarget - ? `选择目录 ${directoryGitActionTarget.path} 下要${directoryGitActionType === "add" ? "添加到VCS" : "回滚"}的文件。` - : "选择要操作的文件。"} + ? directoryGitActionType === "add" + ? t("directoryDialog.descriptionAdd", { + path: directoryGitActionTarget.path, + }) + : t("directoryDialog.descriptionRollback", { + path: directoryGitActionTarget.path, + }) + : t("directoryDialog.descriptionFallback")}
- 已选择 {directoryGitSelectedPaths.size} /{" "} - {directoryGitAllFilePaths.length} 个文件 + {t("directoryDialog.selectionCount", { + selected: directoryGitSelectedPaths.size, + total: directoryGitAllFilePaths.length, + })}
{directoryGitLoading ? (
- 正在加载目录变更... + {t("directoryDialog.loadingCandidates")}
) : directoryGitError ? (
@@ -2132,7 +2171,7 @@ export function FileTreeTab() { ) : (
- 没有可操作的文件 + {t("directoryDialog.noOperableFiles")}
)}
@@ -2143,7 +2182,7 @@ export function FileTreeTab() { disabled={directoryGitSubmitting} onClick={resetDirectoryGitActionDialog} > - 取消 + {tCommon("cancel")}
@@ -2179,29 +2220,35 @@ export function FileTreeTab() { > - 与分支比较 + {t("compareDialog.title")} {compareTarget - ? `选择分支并与${compareTarget.kind === "dir" ? "目录" : "文件"} ${compareTarget.path} 对比` - : "选择要比较的分支。"} + ? t("compareDialog.descriptionWithTarget", { + kind: + compareTarget.kind === "dir" + ? t("compareDialog.kindDirectory") + : t("compareDialog.kindFile"), + path: compareTarget.path, + }) + : t("compareDialog.descriptionFallback")}
setCompareBranchFilter(event.target.value)} - placeholder="过滤分支,例如 main / origin/main" + placeholder={t("compareDialog.filterPlaceholder")} autoFocus disabled={comparing} />
- 单击分支即可直接比较 + {t("compareDialog.singleClickHint")}
{compareBranchLoading ? (
- 正在加载分支... + {t("compareDialog.loadingBranches")}
) : ( <> @@ -2211,7 +2258,9 @@ export function FileTreeTab() { > - 最近分支 ({filteredCompareRecentBranches.length}) + {t("compareDialog.recentBranches", { + count: filteredCompareRecentBranches.length, + })} {filteredCompareRecentBranches.length > 0 ? ( @@ -2232,7 +2281,7 @@ export function FileTreeTab() { )) ) : (
- 无当前分支 + {t("compareDialog.noCurrentBranch")}
)}
@@ -2243,7 +2292,9 @@ export function FileTreeTab() { > - 本地分支 ({filteredCompareBranches.local.length}) + {t("compareDialog.localBranches", { + count: filteredCompareBranches.local.length, + })} {filteredCompareBranches.local.length > 0 ? ( @@ -2264,7 +2315,7 @@ export function FileTreeTab() { )) ) : (
- 无匹配分支 + {t("compareDialog.noMatchingBranches")}
)}
@@ -2275,7 +2326,9 @@ export function FileTreeTab() { > - 远程分支 ({filteredCompareBranches.remote.length}) + {t("compareDialog.remoteBranches", { + count: filteredCompareBranches.remote.length, + })} {filteredCompareBranches.remote.length > 0 ? ( @@ -2296,7 +2349,7 @@ export function FileTreeTab() { )) ) : (
- 无匹配分支 + {t("compareDialog.noMatchingBranches")}
)}
@@ -2316,7 +2369,7 @@ export function FileTreeTab() { setCompareCurrentBranch(null) }} > - 取消 + {tCommon("cancel")}
@@ -2332,11 +2385,13 @@ export function FileTreeTab() { > - 检测到外部文件变更 + {t("externalConflictDialog.title")} {externalConflictPrompt - ? `文件 ${externalConflictPrompt.path} 在磁盘已发生变化,当前编辑内容尚未保存。` - : "当前文件在磁盘已发生变化,当前编辑内容尚未保存。"} + ? t("externalConflictDialog.descriptionWithPath", { + path: externalConflictPrompt.path, + }) + : t("externalConflictDialog.descriptionFallback")} @@ -2346,7 +2401,7 @@ export function FileTreeTab() { disabled={savingExternalConflictCopy} onClick={handleCompareExternalConflict} > - 对比 + {t("externalConflictDialog.compare")} @@ -2379,15 +2436,23 @@ export function FileTreeTab() { > - 确认删除 + {t("deleteConfirm.title")} {deleteTarget - ? `确定删除${deleteTarget.kind === "dir" ? "目录" : "文件"} "${deleteTarget.name}" 吗?此操作不可撤销。` - : "此操作不可撤销。"} + ? t("deleteConfirm.descriptionWithTarget", { + kind: + deleteTarget.kind === "dir" + ? t("deleteConfirm.kindDirectory") + : t("deleteConfirm.kindFile"), + name: deleteTarget.name, + }) + : t("deleteConfirm.descriptionFallback")} - 取消 + + {tCommon("cancel")} + - 删除 + {tCommon("delete")} @@ -2410,15 +2475,19 @@ export function FileTreeTab() { > - 确认回滚 + {t("rollbackConfirm.title")} {rollbackTarget - ? `确定回滚文件 "${rollbackTarget.name}" 的本地修改吗?` - : "确定回滚该文件的本地修改吗?"} + ? t("rollbackConfirm.descriptionWithTarget", { + name: rollbackTarget.name, + }) + : t("rollbackConfirm.descriptionFallback")} - 取消 + + {tCommon("cancel")} + - 回滚 + {t("actions.rollback")} diff --git a/src/components/layout/aux-panel-git-changes-tab.tsx b/src/components/layout/aux-panel-git-changes-tab.tsx index b50f9d0..82da7a6 100644 --- a/src/components/layout/aux-panel-git-changes-tab.tsx +++ b/src/components/layout/aux-panel-git-changes-tab.tsx @@ -10,6 +10,7 @@ import { } from "react" import { listen, type UnlistenFn } from "@tauri-apps/api/event" import { ChevronsDownUp, ChevronsUpDown } from "lucide-react" +import { useTranslations } from "next-intl" import { toast } from "sonner" import { CommitFileAdditions, @@ -423,6 +424,8 @@ function toWorkingTreeChanges( } export function GitChangesTab() { + const t = useTranslations("Folder.gitChangesTab") + const tCommon = useTranslations("Folder.common") const { folder } = useFolderContext() const { activeTab } = useAuxPanelContext() const { openFilePreview, openWorkingTreeDiff } = useWorkspaceContext() @@ -466,8 +469,8 @@ export function GitChangesTab() { const folderName = useMemo(() => { const path = folder?.path ?? "" const parts = path.split(/[\\/]/).filter(Boolean) - return (parts[parts.length - 1] ?? path) || "workspace" - }, [folder?.path]) + return (parts[parts.length - 1] ?? path) || t("workspace") + }, [folder?.path, t]) const trackedChanges = useMemo( () => changes.filter((change) => !isUntrackedStatus(change.status)), @@ -712,8 +715,8 @@ export function GitChangesTab() { resetDirectoryGitActionDialog() toast.info( action === "add" - ? "该目录下没有可添加到VCS的变更文件" - : "该目录下没有可回滚的变更文件" + ? t("toasts.noAddableFilesInDir") + : t("toasts.noRollbackFilesInDir") ) return } @@ -729,7 +732,7 @@ export function GitChangesTab() { setDirectoryGitLoading(false) } }, - [folder?.path, resetDirectoryGitActionDialog] + [folder?.path, resetDirectoryGitActionDialog, t] ) const handleRequestRollback = useCallback( @@ -753,14 +756,14 @@ export function GitChangesTab() { if (!folder?.path) return try { await gitAddFiles(folder.path, [target.path]) - toast.success(`已添加 ${target.name} 到VCS`) + toast.success(t("toasts.addedToVcs", { name: target.name })) await fetchChanges({ inline: true }) } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("添加到VCS失败", { description: message }) + toast.error(t("toasts.addToVcsFailed"), { description: message }) } }, - [fetchChanges, folder?.path, openDirectoryGitActionDialog] + [fetchChanges, folder?.path, openDirectoryGitActionDialog, t] ) const handleRollbackConfirm = useCallback(async () => { @@ -769,16 +772,16 @@ export function GitChangesTab() { setRollingBack(true) try { await gitRollbackFile(folder.path, rollbackTarget.path) - toast.success(`已回滚 ${rollbackTarget.name}`) + toast.success(t("toasts.rolledBack", { name: rollbackTarget.name })) setRollbackTarget(null) await fetchChanges({ inline: true }) } catch (error) { const message = error instanceof Error ? error.message : String(error) - toast.error("回滚失败", { description: message }) + toast.error(t("toasts.rollbackFailed"), { description: message }) } finally { setRollingBack(false) } - }, [fetchChanges, folder?.path, rollbackTarget]) + }, [fetchChanges, folder?.path, rollbackTarget, t]) const directoryGitAllFilePaths = useMemo( () => directoryGitCandidates.map((entry) => entry.path), @@ -830,12 +833,20 @@ export function GitChangesTab() { try { if (directoryGitActionType === "add") { await gitAddFiles(folder.path, selectedPaths) - toast.success(`已添加 ${selectedPaths.length} 个文件到VCS`) + toast.success( + t("toasts.addedFilesToVcs", { + count: selectedPaths.length, + }) + ) } else { for (const filePath of selectedPaths) { await gitRollbackFile(folder.path, filePath) } - toast.success(`已回滚 ${selectedPaths.length} 个文件`) + toast.success( + t("toasts.rolledBackFiles", { + count: selectedPaths.length, + }) + ) } resetDirectoryGitActionDialog() @@ -844,7 +855,9 @@ export function GitChangesTab() { const message = error instanceof Error ? error.message : String(error) setDirectoryGitError(message) toast.error( - directoryGitActionType === "add" ? "添加到VCS失败" : "回滚失败", + directoryGitActionType === "add" + ? t("toasts.addToVcsFailed") + : t("toasts.rollbackFailed"), { description: message, } @@ -858,6 +871,7 @@ export function GitChangesTab() { fetchChanges, folder?.path, resetDirectoryGitActionDialog, + t, ]) useEffect(() => { @@ -893,7 +907,7 @@ export function GitChangesTab() { void openWorkingTreeDiff(node.path, { mode: "overview" }) }} > - 查看差异 + {tCommon("viewDiff")} { @@ -901,14 +915,14 @@ export function GitChangesTab() { }} variant="destructive" > - 回滚 + {t("actions.rollback")} { void handleAddToVcs(target) }} > - 添加到VCS + {t("actions.addToVcs")} @@ -959,14 +973,14 @@ export function GitChangesTab() { void openFilePreview(file.path) }} > - 打开文件 + {tCommon("openFile")} { void openWorkingTreeDiff(file.path) }} > - 查看差异 + {tCommon("viewDiff")} { @@ -974,14 +988,14 @@ export function GitChangesTab() { }} variant="destructive" > - 回滚 + {t("actions.rollback")} { void handleAddToVcs(target) }} > - 添加到VCS + {t("actions.addToVcs")} @@ -992,6 +1006,8 @@ export function GitChangesTab() { handleRequestRollback, openFilePreview, openWorkingTreeDiff, + t, + tCommon, ] ) @@ -1023,7 +1039,7 @@ export function GitChangesTab() { void openWorkingTreeDiff(node.path, { mode: "overview" }) }} > - 查看差异 + {tCommon("viewDiff")} { @@ -1031,14 +1047,14 @@ export function GitChangesTab() { }} variant="destructive" > - 回滚 + {t("actions.rollback")} { void handleAddToVcs(target) }} > - 添加到VCS + {t("actions.addToVcs")} @@ -1079,14 +1095,14 @@ export function GitChangesTab() { void openFilePreview(file.path) }} > - 打开文件 + {tCommon("openFile")} { void openWorkingTreeDiff(file.path) }} > - 查看差异 + {tCommon("viewDiff")} { @@ -1094,14 +1110,14 @@ export function GitChangesTab() { }} variant="destructive" > - 回滚 + {t("actions.rollback")} { void handleAddToVcs(target) }} > - 添加到VCS + {t("actions.addToVcs")} @@ -1112,6 +1128,8 @@ export function GitChangesTab() { handleRequestRollback, openFilePreview, openWorkingTreeDiff, + t, + tCommon, ] ) @@ -1139,14 +1157,16 @@ export function GitChangesTab() {
{trackedChanges.length === 0 && untrackedChanges.length === 0 ? (
- 暂无本地改动 + {t("noChanges")}
) : (
{trackedChanges.length > 0 && (
- 本地已跟踪改动 ({trackedChanges.length}) + + {t("trackedChanges", { count: trackedChanges.length })} +
{directoryGitLoading ? (
- 正在加载目录变更... + {t("directoryDialog.loadingCandidates")}
) : directoryGitError ? (
@@ -1309,7 +1351,7 @@ export function GitChangesTab() {
) : (
- 没有可操作的文件 + {t("directoryDialog.noOperableFiles")}
)}
@@ -1320,7 +1362,7 @@ export function GitChangesTab() { disabled={directoryGitSubmitting} onClick={resetDirectoryGitActionDialog} > - 取消 + {tCommon("cancel")}
@@ -1354,15 +1398,23 @@ export function GitChangesTab() { > - 确认回滚 + {t("rollbackConfirm.title")} {rollbackTarget - ? `确定回滚${rollbackTarget.kind === "dir" ? "目录" : "文件"} "${rollbackTarget.name}" 的本地修改吗?` - : "确定回滚本地修改吗?"} + ? t("rollbackConfirm.descriptionWithTarget", { + kind: + rollbackTarget.kind === "dir" + ? t("rollbackConfirm.kindDirectory") + : t("rollbackConfirm.kindFile"), + name: rollbackTarget.name, + }) + : t("rollbackConfirm.descriptionFallback")} - 取消 + + {tCommon("cancel")} + - 回滚 + {t("actions.rollback")} diff --git a/src/components/layout/aux-panel-git-log-tab.tsx b/src/components/layout/aux-panel-git-log-tab.tsx index c17cd49..b195100 100644 --- a/src/components/layout/aux-panel-git-log-tab.tsx +++ b/src/components/layout/aux-panel-git-log-tab.tsx @@ -8,6 +8,7 @@ import { useMemo, useState, } from "react" +import { useTranslations } from "next-intl" import { ChevronsDownUp, ChevronsUpDown, @@ -83,7 +84,18 @@ import { import type { GitBranchList, GitLogEntry, GitLogFileChange } from "@/lib/types" import { toast } from "sonner" -function formatRelativeTime(dateStr: string): string { +function formatRelativeTime( + dateStr: string, + t: ( + key: + | "time.monthsAgo" + | "time.daysAgo" + | "time.hoursAgo" + | "time.minsAgo" + | "time.justNow", + values?: { count: number } + ) => string +): string { const date = new Date(dateStr) if (Number.isNaN(date.getTime())) return dateStr @@ -95,13 +107,12 @@ function formatRelativeTime(dateStr: string): string { if (diffDay > 30) { const diffMonth = Math.floor(diffDay / 30) - return diffMonth === 1 ? "1 month ago" : `${diffMonth} months ago` + return t("time.monthsAgo", { count: diffMonth }) } - if (diffDay > 0) return diffDay === 1 ? "1 day ago" : `${diffDay} days ago` - if (diffHour > 0) - return diffHour === 1 ? "1 hour ago" : `${diffHour} hours ago` - if (diffMin > 0) return diffMin === 1 ? "1 min ago" : `${diffMin} mins ago` - return "just now" + if (diffDay > 0) return t("time.daysAgo", { count: diffDay }) + if (diffHour > 0) return t("time.hoursAgo", { count: diffHour }) + if (diffMin > 0) return t("time.minsAgo", { count: diffMin }) + return t("time.justNow", { count: 0 }) } function parseDate(dateStr: string): Date | null { @@ -137,14 +148,21 @@ function mapFileStatus( } } -function getPushStatusMeta(pushed: boolean | null): { +function getPushStatusMeta( + pushed: boolean | null, + labels: { + pushed: string + notPushed: string + unknown: string + } +): { label: string icon: typeof CloudCheck className: string } { if (pushed === true) { return { - label: "Pushed to remote", + label: labels.pushed, icon: CloudCheck, className: "text-emerald-500", } @@ -152,14 +170,14 @@ function getPushStatusMeta(pushed: boolean | null): { if (pushed === false) { return { - label: "Not pushed to remote", + label: labels.notPushed, icon: CloudOff, className: "text-amber-500", } } return { - label: "Push status unknown (no upstream configured)", + label: labels.unknown, icon: CircleHelp, className: "text-muted-foreground", } @@ -348,6 +366,8 @@ function CommitFilesTree({ ) => void onOpenFilePreview: (path: string) => void }) { + const t = useTranslations("Folder.gitLogTab") + const tCommon = useTranslations("Folder.common") const rootPath = "__commit_file_tree_root__" const treeNodes = useMemo(() => buildCommitFileTree(files), [files]) const allDirectoryPaths = useMemo(() => { @@ -431,14 +451,14 @@ function CommitFilesTree({ void onOpenCommitDiff(commitHash, file.path) }} > - 查看差异 + {tCommon("viewDiff")} { void onOpenFilePreview(file.path) }} > - 打开文件 + {tCommon("openFile")} @@ -448,7 +468,7 @@ function CommitFilesTree({ return (
-

Files

+

{t("filesTitle")}

diff --git a/src/components/layout/branch-dropdown.tsx b/src/components/layout/branch-dropdown.tsx index 1083d9c..0937885 100644 --- a/src/components/layout/branch-dropdown.tsx +++ b/src/components/layout/branch-dropdown.tsx @@ -57,6 +57,7 @@ import { CollapsibleTrigger, } from "@/components/ui/collapsible" import { ScrollArea } from "@/components/ui/scroll-area" +import { useTranslations } from "next-intl" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" @@ -106,6 +107,8 @@ export function BranchDropdown({ parentBranch, onBranchChange, }: BranchDropdownProps) { + const t = useTranslations("Folder.branchDropdown") + const tCommon = useTranslations("Folder.common") const { folder } = useFolderContext() const folderPath = folder?.path ?? "" const { addTask, updateTask, removeTask } = useTaskContext() @@ -142,8 +145,10 @@ export function BranchDropdown({ "folder://git-commit-succeeded", (event) => { if (event.payload.folder_id !== folder.id) return - toast.success("提交代码完成", { - description: `已提交 ${event.payload.committed_files} 个文件`, + toast.success(t("toasts.commitCodeCompleted"), { + description: t("toasts.committedFiles", { + count: event.payload.committed_files, + }), }) onBranchChange() } @@ -158,7 +163,7 @@ export function BranchDropdown({ return () => { if (unlisten) unlisten() } - }, [folder, onBranchChange]) + }, [folder, onBranchChange, t]) async function runGitTask( label: string, @@ -175,7 +180,7 @@ export function BranchDropdown({ updateTask(taskId, { status: "completed" }) onBranchChange() toast.success( - `${label} 完成`, + t("toasts.taskCompleted", { label }), successDescription ? { description: successDescription, @@ -184,8 +189,9 @@ export function BranchDropdown({ ) } catch (err) { removeTask(taskId) - pushAlert("error", `${label}失败`, String(err)) - toast.error(`${label} 失败`, { description: String(err) }) + const errorTitle = t("toasts.taskFailed", { label }) + pushAlert("error", errorTitle, String(err)) + toast.error(errorTitle, { description: String(err) }) } finally { setLoading(false) } @@ -220,7 +226,9 @@ export function BranchDropdown({ if (!name) return setNewBranchOpen(false) setNewBranchName("") - await runGitTask(`新建分支 ${name}`, () => gitNewBranch(folderPath, name)) + await runGitTask(t("tasks.newBranch", { name }), () => + gitNewBranch(folderPath, name) + ) } function handleOpenWorktreeDialog() { @@ -254,7 +262,7 @@ export function BranchDropdown({ const wtPath = worktreePath.trim() if (!name || !wtPath) return setWorktreeOpen(false) - await runGitTask(`新建工作树 ${name}`, async () => { + await runGitTask(t("tasks.newWorktree", { name }), async () => { await gitWorktreeAdd(folderPath, name, wtPath) await openFolderWindow(wtPath) await setFolderParentBranch(wtPath, branch) @@ -268,7 +276,7 @@ export function BranchDropdown({ async function handleCheckout(branchName: string) { setDropdownOpen(false) - await runGitTask(`切换到 ${branchName}`, () => + await runGitTask(t("tasks.checkoutTo", { branchName }), () => gitCheckout(folderPath, branchName) ) } @@ -276,7 +284,7 @@ export function BranchDropdown({ async function handleCheckoutRemote(remoteBranch: string) { const localName = remoteBranch.replace(/^[^/]+\//, "") setDropdownOpen(false) - await runGitTask(`切换到 ${localName}`, () => + await runGitTask(t("tasks.checkoutTo", { branchName: localName }), () => gitCheckout(folderPath, localName) ) } @@ -289,23 +297,23 @@ export function BranchDropdown({ switch (type) { case "merge": await runGitTask( - `合并 ${branchName}`, + t("tasks.mergeBranch", { branchName }), () => gitMerge(folderPath, branchName), (result) => { if (result.merged_commits === 0) { - return `${branchName} 没有新的提交` + return t("toasts.mergeNoNewCommits", { branchName }) } - return `已合并 ${result.merged_commits} 个提交` + return t("toasts.mergedCommits", { count: result.merged_commits }) } ) break case "rebase": - await runGitTask(`变基到 ${branchName}`, () => + await runGitTask(t("tasks.rebaseTo", { branchName }), () => gitRebase(folderPath, branchName) ) break case "delete": - await runGitTask(`删除分支 ${branchName}`, () => + await runGitTask(t("tasks.deleteBranch", { branchName }), () => gitDeleteBranch(folderPath, branchName) ) break @@ -316,11 +324,11 @@ export function BranchDropdown({ if (!confirmAction) return "" switch (confirmAction.type) { case "merge": - return "合并分支" + return t("confirm.mergeTitle") case "rebase": - return "变基分支" + return t("confirm.rebaseTitle") case "delete": - return "删除分支" + return t("confirm.deleteTitle") } } @@ -328,11 +336,19 @@ export function BranchDropdown({ if (!confirmAction) return "" switch (confirmAction.type) { case "merge": - return `确定将 ${confirmAction.branchName} 合并到当前分支 ${branch} 吗?` + return t("confirm.mergeDescription", { + branchName: confirmAction.branchName, + currentBranch: branch ?? "-", + }) case "rebase": - return `确定将当前分支 ${branch} 变基到 ${confirmAction.branchName} 吗?` + return t("confirm.rebaseDescription", { + currentBranch: branch ?? "-", + branchName: confirmAction.branchName, + }) case "delete": - return `确定删除分支 ${confirmAction.branchName} 吗?此操作不可恢复。` + return t("confirm.deleteDescription", { + branchName: confirmAction.branchName, + }) } } @@ -351,7 +367,7 @@ export function BranchDropdown({ > {b} - 当前 + {t("current")}
) } @@ -393,7 +409,7 @@ export function BranchDropdown({ }} > - 切换到此分支 + {t("switchToBranch")} { @@ -401,7 +417,11 @@ export function BranchDropdown({ setConfirmAction({ type: "merge", branchName: b }) }} > - 将 {b} 合并到 {branch} + + {t("mergeBranchIntoCurrent", { + branchName: b, + currentBranch: branch ?? "-", + })} { @@ -409,8 +429,11 @@ export function BranchDropdown({ setConfirmAction({ type: "rebase", branchName: b }) }} > - 将 {branch} 变基到{" "} - {b} + + {t("rebaseCurrentToBranch", { + currentBranch: branch ?? "-", + branchName: b, + })} {!isRemote && ( <> @@ -423,7 +446,7 @@ export function BranchDropdown({ }} > - 删除分支 + {t("deleteBranch")} )} @@ -438,7 +461,7 @@ export function BranchDropdown({ @@ -446,11 +469,11 @@ export function BranchDropdown({ - runGitTask("初始化 Git 仓库", () => gitInit(folderPath)) + runGitTask(t("tasks.initGitRepo"), () => gitInit(folderPath)) } > - 初始化 Git 仓库 + {t("initGitRepo")} @@ -473,28 +496,30 @@ export function BranchDropdown({ disabled={loading} onSelect={() => runGitTask( - "更新代码", + t("tasks.pullCode"), () => gitPull(folderPath), (result) => { if (result.updated_files === 0) { - return "所有文件均为最新版本" + return t("toasts.allFilesUpToDate") } - return `已更新 ${result.updated_files} 个文件` + return t("toasts.updatedFiles", { + count: result.updated_files, + }) } ) } > - 更新代码 + {t("pullCode")} - runGitTask("获取信息", () => gitFetch(folderPath)) + runGitTask(t("tasks.fetchInfo"), () => gitFetch(folderPath)) } > - 提取远程分支 + {t("fetchRemoteBranches")} @@ -505,37 +530,42 @@ export function BranchDropdown({ if (!folder) return setDropdownOpen(false) openCommitWindow(folder.id).catch((err) => { - pushAlert("error", "打开提交窗口失败", String(err)) - toast.error("打开提交窗口失败", { description: String(err) }) + const title = t("toasts.openCommitWindowFailed") + pushAlert("error", title, String(err)) + toast.error(title, { description: String(err) }) }) }} > - 提交代码... + {t("openCommitWindow")} runGitTask( - "推送代码", + t("tasks.pushCode"), () => gitPush(folderPath), (result) => { if (result.upstream_set) { if (result.pushed_commits === 0) { - return "已设置远程跟踪分支" + return t("toasts.upstreamSet") } - return `已设置远程跟踪分支并推送 ${result.pushed_commits} 个提交` + return t("toasts.upstreamSetAndPushed", { + count: result.pushed_commits, + }) } if (result.pushed_commits === 0) { - return "没有可推送的提交" + return t("toasts.noCommitsToPush") } - return `已推送 ${result.pushed_commits} 个提交` + return t("toasts.pushedCommits", { + count: result.pushed_commits, + }) } ) } > - 推送... + {t("pushCode")} @@ -548,14 +578,14 @@ export function BranchDropdown({ }} > - 新建分支... + {t("newBranch")} - 新建工作树... + {t("newWorktree")} @@ -563,20 +593,20 @@ export function BranchDropdown({ - runGitTask("贮藏更改", () => gitStash(folderPath)) + runGitTask(t("tasks.stashChanges"), () => gitStash(folderPath)) } > - 贮藏更改 + {t("stashChanges")} - runGitTask("取消贮藏", () => gitStashPop(folderPath)) + runGitTask(t("tasks.stashPop"), () => gitStashPop(folderPath)) } > - 取消贮藏... + {t("stashPop")} @@ -589,11 +619,13 @@ export function BranchDropdown({ - 本地分支 ({branchList.local.length}) + {t("localBranches", { count: branchList.local.length })} {branchList.local.length === 0 ? ( - 无本地分支 + + {t("noLocalBranches")} + ) : ( branchList.local.map((b) => renderBranchItem(b, false)) )} @@ -603,11 +635,13 @@ export function BranchDropdown({ - 远程分支 ({branchList.remote.length}) + {t("remoteBranches", { count: branchList.remote.length })} {branchList.remote.length === 0 ? ( - 无远程分支 + + {t("noRemoteBranches")} + ) : ( branchList.remote.map((b) => renderBranchItem(b, true)) )} @@ -623,7 +657,7 @@ export function BranchDropdown({ className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs text-orange-500 dark:text-orange-400 hover:bg-accent hover:text-orange-600 dark:hover:text-orange-300 transition-colors cursor-default select-none" disabled={loading} onClick={handleMergeParent} - title={`当前分支从 ${parentBranch} 创建,点击合并 ${parentBranch} 到当前分支`} + title={t("parentBranchHint", { parentBranch })} > {parentBranch} @@ -644,14 +678,14 @@ export function BranchDropdown({ - 取消 + {tCommon("cancel")} - 确定 + {tCommon("confirm")} @@ -660,13 +694,13 @@ export function BranchDropdown({ - 新建分支 + {t("dialogs.newBranchTitle")} - 从当前分支 {branch} 创建新分支 + {t("dialogs.newBranchDescription", { branch: branch ?? "-" })} setNewBranchName(e.target.value)} onKeyDown={(e) => { @@ -677,13 +711,13 @@ export function BranchDropdown({ /> @@ -692,17 +726,17 @@ export function BranchDropdown({ - 新建工作树 + {t("dialogs.newWorktreeTitle")} - 从当前分支 {branch} 创建新的工作树 + {t("dialogs.newWorktreeDescription", { branch: branch ?? "-" })}
- + handleWorktreeBranchChange(e.target.value)} onKeyDown={(e) => { @@ -713,11 +747,11 @@ export function BranchDropdown({ />
- +
setWorktreePath(e.target.value)} className="flex-1" @@ -734,7 +768,7 @@ export function BranchDropdown({
diff --git a/src/components/layout/command-dropdown.tsx b/src/components/layout/command-dropdown.tsx index 1bcaa43..b5273d5 100644 --- a/src/components/layout/command-dropdown.tsx +++ b/src/components/layout/command-dropdown.tsx @@ -3,6 +3,7 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event" import { useState, useEffect, useCallback, useMemo, useRef } from "react" import { ChevronDown, Play, Plus, Square } from "lucide-react" +import { useTranslations } from "next-intl" import { Button } from "@/components/ui/button" import { DropdownMenu, @@ -40,6 +41,7 @@ function setSelectedCommandId(folderId: number, cmdId: number) { } export function CommandDropdown() { + const t = useTranslations("Folder.commandDropdown") const { folder } = useFolderContext() const { createTerminalWithCommand } = useTerminalContext() const [commands, setCommands] = useState([]) @@ -273,7 +275,7 @@ export function CommandDropdown() { if (!folder) return null - // No commands → show "Add Command" + // No commands → show add command button if (commands.length === 0) { return ( <> @@ -285,7 +287,7 @@ export function CommandDropdown() { disabled={bootstrapping} > - {bootstrapping ? "Loading..." : "Add Command"} + {bootstrapping ? t("loading") : t("addCommand")} setManageOpen(true)}> - Manage Commands... + {t("manageCommands")} @@ -341,8 +343,8 @@ export function CommandDropdown() { onClick={handleRunOrStop} title={ isActiveCommandRunning - ? `Stop: ${activeCmd?.command}` - : `Run: ${activeCmd?.command}` + ? t("stopCommandTitle", { command: activeCmd?.command ?? "" }) + : t("runCommandTitle", { command: activeCmd?.command ?? "" }) } > {isActiveCommandRunning ? ( diff --git a/src/components/layout/command-manage-dialog.tsx b/src/components/layout/command-manage-dialog.tsx index e950abb..b622d89 100644 --- a/src/components/layout/command-manage-dialog.tsx +++ b/src/components/layout/command-manage-dialog.tsx @@ -1,6 +1,7 @@ "use client" import { useState, useEffect } from "react" +import { useTranslations } from "next-intl" import { Dialog, DialogContent, @@ -41,6 +42,8 @@ export function CommandManageDialog({ commands, onSaved, }: CommandManageDialogProps) { + const t = useTranslations("Folder.commandDropdown.manageDialog") + const tCommon = useTranslations("Folder.common") const [drafts, setDrafts] = useState([]) const [saving, setSaving] = useState(false) @@ -115,13 +118,13 @@ export function CommandManageDialog({ - Manage Commands + {t("title")}
{visibleDrafts.length === 0 && (

- No commands yet + {t("empty")}

)} {drafts.map( @@ -129,7 +132,7 @@ export function CommandManageDialog({ !draft.deleted && (
updateDraft(index, "name", e.target.value) @@ -137,7 +140,7 @@ export function CommandManageDialog({ className="h-8 text-sm flex-1" /> updateDraft(index, "command", e.target.value) @@ -160,7 +163,7 @@ export function CommandManageDialog({
diff --git a/src/components/layout/commit-dialog.tsx b/src/components/layout/commit-dialog.tsx index 2c28b79..aea520e 100644 --- a/src/components/layout/commit-dialog.tsx +++ b/src/components/layout/commit-dialog.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { Check, ChevronDown, ChevronRight, Loader2 } from "lucide-react" +import { useTranslations } from "next-intl" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Textarea } from "@/components/ui/textarea" @@ -204,6 +205,8 @@ export function CommitWorkspace({ onCommitted, onCancel, }: CommitWorkspaceProps) { + const t = useTranslations("Folder.commitDialog") + const tCommon = useTranslations("Folder.common") const [entries, setEntries] = useState([]) const containerRef = useRef(null) const [containerWidth, setContainerWidth] = useState(0) @@ -416,8 +419,10 @@ export function CommitWorkspace({ commitMessage, Array.from(selected) ) - toast.success("提交代码完成", { - description: `已提交 ${result.committed_files} 个文件`, + toast.success(t("toasts.commitCompleted"), { + description: t("toasts.committedFiles", { + count: result.committed_files, + }), }) onCommitted?.() } catch (err) { @@ -425,7 +430,7 @@ export function CommitWorkspace({ } finally { setCommitting(false) } - }, [folderPath, onCommitted, selected]) + }, [folderPath, onCommitted, selected, t]) // --- Context menu actions --- @@ -434,28 +439,28 @@ export function CommitWorkspace({ if (!folderPath) return try { await gitAddFiles(folderPath, [file]) - toast.success("已添加到 VCS", { description: file }) + toast.success(t("toasts.addedToVcs"), { description: file }) void loadStatus() } catch (err) { - toast.error("添加到 VCS 失败", { description: String(err) }) + toast.error(t("toasts.addToVcsFailed"), { description: String(err) }) } }, - [folderPath, loadStatus] + [folderPath, loadStatus, t] ) const handleDeleteFile = useCallback( (file: string) => { setConfirm({ open: true, - title: "确认删除", - description: `确定要删除文件「${file}」吗?此操作不可恢复。`, + title: t("confirm.deleteTitle"), + description: t("confirm.deleteDescription", { file }), variant: "destructive", action: () => { void (async () => { if (!folderPath) return try { await deleteFileTreeEntry(folderPath, file) - toast.success("文件已删除", { description: file }) + toast.success(t("toasts.fileDeleted"), { description: file }) // If deleted file was being viewed, clear the diff if (diffFileRef.current === file) { setDiffFile(null) @@ -470,28 +475,30 @@ export function CommitWorkspace({ }) void loadStatus() } catch (err) { - toast.error("删除失败", { description: String(err) }) + toast.error(t("toasts.deleteFailed"), { + description: String(err), + }) } })() }, }) }, - [folderPath, loadStatus] + [folderPath, loadStatus, t] ) const handleRollbackFile = useCallback( (file: string) => { setConfirm({ open: true, - title: "确认回滚", - description: `确定要回滚文件「${file}」到 HEAD 版本吗?未保存的修改将丢失。`, + title: t("confirm.rollbackTitle"), + description: t("confirm.rollbackDescription", { file }), variant: "destructive", action: () => { void (async () => { if (!folderPath) return try { await gitRollbackFile(folderPath, file) - toast.success("文件已回滚", { description: file }) + toast.success(t("toasts.fileRolledBack"), { description: file }) if (diffFileRef.current === file) { setDiffFile(null) setDiffOriginal("") @@ -505,13 +512,15 @@ export function CommitWorkspace({ }) void loadStatus() } catch (err) { - toast.error("回滚失败", { description: String(err) }) + toast.error(t("toasts.rollbackFailed"), { + description: String(err), + }) } })() }, }) }, - [folderPath, loadStatus] + [folderPath, loadStatus, t] ) const closeConfirm = useCallback(() => { @@ -631,7 +640,12 @@ export function CommitWorkspace({ ? "border-primary bg-primary text-primary-foreground" : "border-input" )} - aria-label={`${selected.has(node.path) ? "取消选择" : "选择"} ${node.path}`} + aria-label={t("aria.selectFile", { + action: selected.has(node.path) + ? t("actions.unselect") + : t("actions.select"), + path: node.path, + })} > {selected.has(node.path) && } @@ -654,20 +668,28 @@ export function CommitWorkspace({ {!isDeleted && ( handleRollbackFile(node.path)}> - 回滚 + {t("actions.rollback")} )} handleDeleteFile(node.path)} > - 删除 + {tCommon("delete")} ) }, - [selected, toggleFile, handleViewDiff, handleRollbackFile, handleDeleteFile] + [ + selected, + toggleFile, + handleViewDiff, + handleRollbackFile, + handleDeleteFile, + t, + tCommon, + ] ) const renderUntrackedNode = useCallback( @@ -704,7 +726,12 @@ export function CommitWorkspace({ ? "border-primary bg-primary text-primary-foreground" : "border-input" )} - aria-label={`${selected.has(node.path) ? "取消选择" : "选择"} ${node.path}`} + aria-label={t("aria.selectFile", { + action: selected.has(node.path) + ? t("actions.unselect") + : t("actions.select"), + path: node.path, + })} > {selected.has(node.path) && } @@ -723,20 +750,28 @@ export function CommitWorkspace({ handleAddToVcs(node.path)}> - 添加到 VCS + {t("actions.addToVcs")} handleDeleteFile(node.path)} > - 删除 + {tCommon("delete")} ) }, - [selected, toggleFile, handleViewDiff, handleAddToVcs, handleDeleteFile] + [ + selected, + toggleFile, + handleViewDiff, + handleAddToVcs, + handleDeleteFile, + t, + tCommon, + ] ) const toggleTrackedGroup = useCallback( @@ -783,14 +818,21 @@ export function CommitWorkspace({ ? "border-primary bg-primary text-primary-foreground" : "border-input" )} - aria-label={allSelected ? "取消选择全部文件" : "选择全部文件"} + aria-label={ + allSelected + ? t("aria.unselectAllFiles") + : t("aria.selectAllFiles") + } > {allSelected && } {loadingStatus - ? "加载中..." - : `${selected.size} / ${entries.length} 个文件`} + ? t("loading") + : t("selectionCount", { + selected: selected.size, + total: entries.length, + })}
@@ -798,7 +840,7 @@ export function CommitWorkspace({ {entries.length === 0 && !loadingStatus ? (
- 没有改动的文件 + {t("emptyFiles")}
) : (
@@ -816,15 +858,19 @@ export function CommitWorkspace({ )} aria-label={ trackedAllSelected - ? "取消选择已跟踪改动" - : "选择已跟踪改动" + ? t("aria.unselectTracked") + : t("aria.selectTracked") } > {trackedAllSelected && ( )} - 已跟踪改动 ({trackedEntries.length}) + + {t("trackedChanges", { + count: trackedEntries.length, + })} +
{untrackedAllSelected && ( @@ -872,7 +918,11 @@ export function CommitWorkspace({ ) : ( )} - 未跟踪文件 ({untrackedEntries.length}) + + {t("untrackedFiles", { + count: untrackedEntries.length, + })} +
{untrackedOpen && ( @@ -896,17 +946,19 @@ export function CommitWorkspace({
-
提交消息
+
+ {t("commitMessage")} +