folder页面全量多语言处理

This commit is contained in:
xintaofei
2026-03-07 13:44:40 +08:00
parent 3ddc8f165a
commit a356b813a6
10 changed files with 1533 additions and 343 deletions

View File

@@ -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<T>(
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({
>
<BranchIcon className="h-3.5 w-3.5 shrink-0" />
<span className="truncate">{b}</span>
<span className="ml-auto text-xs"></span>
<span className="ml-auto text-xs">{t("current")}</span>
</div>
)
}
@@ -393,7 +409,7 @@ export function BranchDropdown({
}}
>
<GitBranch className="h-3.5 w-3.5" />
{t("switchToBranch")}
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
@@ -401,7 +417,11 @@ export function BranchDropdown({
setConfirmAction({ type: "merge", branchName: b })
}}
>
<GitMerge className="h-3.5 w-3.5" /> {b} {branch}
<GitMerge className="h-3.5 w-3.5" />
{t("mergeBranchIntoCurrent", {
branchName: b,
currentBranch: branch ?? "-",
})}
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
@@ -409,8 +429,11 @@ export function BranchDropdown({
setConfirmAction({ type: "rebase", branchName: b })
}}
>
<GitPullRequestArrow className="h-3.5 w-3.5" /> {branch} {" "}
{b}
<GitPullRequestArrow className="h-3.5 w-3.5" />
{t("rebaseCurrentToBranch", {
currentBranch: branch ?? "-",
branchName: b,
})}
</DropdownMenuItem>
{!isRemote && (
<>
@@ -423,7 +446,7 @@ export function BranchDropdown({
}}
>
<Trash2 className="h-3.5 w-3.5" />
{t("deleteBranch")}
</DropdownMenuItem>
</>
)}
@@ -438,7 +461,7 @@ export function BranchDropdown({
<DropdownMenuTrigger asChild>
<button className="flex items-center gap-1 text-sm tracking-tight hover:text-foreground/80 transition-colors outline-none cursor-default">
<GitFork className="h-3 w-3 shrink-0" />
<span className="truncate"></span>
<span className="truncate">{t("versionControl")}</span>
<ChevronDown className="h-3 w-3 shrink-0 opacity-50" />
</button>
</DropdownMenuTrigger>
@@ -446,11 +469,11 @@ export function BranchDropdown({
<DropdownMenuItem
disabled={loading}
onSelect={() =>
runGitTask("初始化 Git 仓库", () => gitInit(folderPath))
runGitTask(t("tasks.initGitRepo"), () => gitInit(folderPath))
}
>
<GitBranch className="h-3.5 w-3.5" />
Git
{t("initGitRepo")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -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,
})
}
)
}
>
<ArrowDownToLine className="h-3.5 w-3.5" />
{t("pullCode")}
</DropdownMenuItem>
<DropdownMenuItem
disabled={loading}
onSelect={() =>
runGitTask("获取信息", () => gitFetch(folderPath))
runGitTask(t("tasks.fetchInfo"), () => gitFetch(folderPath))
}
>
<RefreshCw className="h-3.5 w-3.5" />
{t("fetchRemoteBranches")}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
@@ -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) })
})
}}
>
<GitCommitHorizontal className="h-3.5 w-3.5" />
...
{t("openCommitWindow")}
</DropdownMenuItem>
<DropdownMenuItem
disabled={loading}
onSelect={() =>
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,
})
}
)
}
>
<Upload className="h-3.5 w-3.5" />
...
{t("pushCode")}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
@@ -548,14 +578,14 @@ export function BranchDropdown({
}}
>
<GitBranchPlus className="h-3.5 w-3.5" />
...
{t("newBranch")}
</DropdownMenuItem>
<DropdownMenuItem
disabled={loading}
onSelect={handleOpenWorktreeDialog}
>
<FolderGit2 className="h-3.5 w-3.5" />
...
{t("newWorktree")}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
@@ -563,20 +593,20 @@ export function BranchDropdown({
<DropdownMenuItem
disabled={loading}
onSelect={() =>
runGitTask("贮藏更改", () => gitStash(folderPath))
runGitTask(t("tasks.stashChanges"), () => gitStash(folderPath))
}
>
<Archive className="h-3.5 w-3.5" />
{t("stashChanges")}
</DropdownMenuItem>
<DropdownMenuItem
disabled={loading}
onSelect={() =>
runGitTask("取消贮藏", () => gitStashPop(folderPath))
runGitTask(t("tasks.stashPop"), () => gitStashPop(folderPath))
}
>
<ArchiveRestore className="h-3.5 w-3.5" />
...
{t("stashPop")}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
@@ -589,11 +619,13 @@ export function BranchDropdown({
<Collapsible open={localOpen} onOpenChange={setLocalOpen}>
<CollapsibleTrigger className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground select-none outline-hidden">
<ChevronRight className="h-3.5 w-3.5 shrink-0 transition-transform [[data-state=open]>&]:rotate-90" />
({branchList.local.length})
{t("localBranches", { count: branchList.local.length })}
</CollapsibleTrigger>
<CollapsibleContent>
{branchList.local.length === 0 ? (
<DropdownMenuItem disabled></DropdownMenuItem>
<DropdownMenuItem disabled>
{t("noLocalBranches")}
</DropdownMenuItem>
) : (
branchList.local.map((b) => renderBranchItem(b, false))
)}
@@ -603,11 +635,13 @@ export function BranchDropdown({
<Collapsible open={remoteOpen} onOpenChange={setRemoteOpen}>
<CollapsibleTrigger className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground select-none outline-hidden">
<ChevronRight className="h-3.5 w-3.5 shrink-0 transition-transform [[data-state=open]>&]:rotate-90" />
({branchList.remote.length})
{t("remoteBranches", { count: branchList.remote.length })}
</CollapsibleTrigger>
<CollapsibleContent>
{branchList.remote.length === 0 ? (
<DropdownMenuItem disabled></DropdownMenuItem>
<DropdownMenuItem disabled>
{t("noRemoteBranches")}
</DropdownMenuItem>
) : (
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 })}
>
<ArrowLeftRight className="h-3 w-3 shrink-0" />
<span className="truncate max-w-32">{parentBranch}</span>
@@ -644,14 +678,14 @@ export function BranchDropdown({
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogCancel>{tCommon("cancel")}</AlertDialogCancel>
<AlertDialogAction
variant={
confirmAction?.type === "delete" ? "destructive" : "default"
}
onClick={handleConfirm}
>
{tCommon("confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
@@ -660,13 +694,13 @@ export function BranchDropdown({
<Dialog open={newBranchOpen} onOpenChange={setNewBranchOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogTitle>{t("dialogs.newBranchTitle")}</DialogTitle>
<DialogDescription>
{branch}
{t("dialogs.newBranchDescription", { branch: branch ?? "-" })}
</DialogDescription>
</DialogHeader>
<Input
placeholder="分支名称"
placeholder={t("dialogs.branchNamePlaceholder")}
value={newBranchName}
onChange={(e) => setNewBranchName(e.target.value)}
onKeyDown={(e) => {
@@ -677,13 +711,13 @@ export function BranchDropdown({
/>
<DialogFooter>
<Button variant="outline" onClick={() => setNewBranchOpen(false)}>
{tCommon("cancel")}
</Button>
<Button
disabled={!newBranchName.trim() || loading}
onClick={handleNewBranch}
>
{tCommon("create")}
</Button>
</DialogFooter>
</DialogContent>
@@ -692,17 +726,17 @@ export function BranchDropdown({
<Dialog open={worktreeOpen} onOpenChange={setWorktreeOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogTitle>{t("dialogs.newWorktreeTitle")}</DialogTitle>
<DialogDescription>
{branch}
{t("dialogs.newWorktreeDescription", { branch: branch ?? "-" })}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
<div className="space-y-2">
<Label htmlFor="wt-branch"></Label>
<Label htmlFor="wt-branch">{t("dialogs.branchNameLabel")}</Label>
<Input
id="wt-branch"
placeholder="分支名称"
placeholder={t("dialogs.branchNamePlaceholder")}
value={worktreeBranchName}
onChange={(e) => handleWorktreeBranchChange(e.target.value)}
onKeyDown={(e) => {
@@ -713,11 +747,11 @@ export function BranchDropdown({
/>
</div>
<div className="space-y-2">
<Label htmlFor="wt-path"></Label>
<Label htmlFor="wt-path">{t("dialogs.worktreePathLabel")}</Label>
<div className="flex gap-2">
<Input
id="wt-path"
placeholder="工作树路径"
placeholder={t("dialogs.worktreePathPlaceholder")}
value={worktreePath}
onChange={(e) => setWorktreePath(e.target.value)}
className="flex-1"
@@ -734,7 +768,7 @@ export function BranchDropdown({
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setWorktreeOpen(false)}>
{tCommon("cancel")}
</Button>
<Button
disabled={
@@ -742,7 +776,7 @@ export function BranchDropdown({
}
onClick={handleNewWorktree}
>
{tCommon("create")}
</Button>
</DialogFooter>
</DialogContent>