feat(i18n): translate chat session delete + project resources section

Two features main shipped while this branch was idle never went through
the i18n pass:

- Chat session delete confirmation dialog (#2115) and history toggle
  tooltip (#2117): adds session_history.delete_dialog.* and
  session_history.row_delete_*, plus window.history_show_tooltip /
  history_back_tooltip.
- Project resources sidebar (#1926/#2080/#2111): entire component
  including toasts, popover form, attach/remove tooltips. New
  projects.resources subtree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-05-06 16:11:06 +08:00
parent cfb286660f
commit 842efd48e6
7 changed files with 83 additions and 24 deletions

View File

@@ -124,22 +124,25 @@ export function ChatSessionHistory() {
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete chat session</AlertDialogTitle>
<AlertDialogTitle>{t(($) => $.session_history.delete_dialog.title)}</AlertDialogTitle>
<AlertDialogDescription>
{pendingDelete?.title
? `"${pendingDelete.title}" and its messages will be permanently removed.`
: "This chat session and its messages will be permanently removed."}
{" "}This action cannot be undone.
? t(($) => $.session_history.delete_dialog.description_with_title, { title: pendingDelete.title })
: t(($) => $.session_history.delete_dialog.description_default)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleteSession.isPending}>Cancel</AlertDialogCancel>
<AlertDialogCancel disabled={deleteSession.isPending}>
{t(($) => $.session_history.delete_dialog.cancel)}
</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmDelete}
disabled={deleteSession.isPending}
className="bg-destructive text-white hover:bg-destructive/90"
>
{deleteSession.isPending ? "Deleting..." : "Delete"}
{deleteSession.isPending
? t(($) => $.session_history.delete_dialog.confirming)
: t(($) => $.session_history.delete_dialog.confirm)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
@@ -228,13 +231,13 @@ function SessionItem({
e.stopPropagation();
onRequestDelete();
}}
aria-label="Delete chat session"
aria-label={t(($) => $.session_history.row_delete_aria)}
/>
}
>
<Trash2 className="size-3.5" />
</TooltipTrigger>
<TooltipContent side="left">Delete</TooltipContent>
<TooltipContent side="left">{t(($) => $.session_history.row_delete_tooltip)}</TooltipContent>
</Tooltip>
</div>
);

View File

@@ -426,7 +426,7 @@ export function ChatWindow() {
<History />
</TooltipTrigger>
<TooltipContent side="top">
{showHistory ? "Back to chat" : "Chat history"}
{showHistory ? t(($) => $.window.history_back_tooltip) : t(($) => $.window.history_show_tooltip)}
</TooltipContent>
</Tooltip>
<Tooltip>

View File

@@ -31,6 +31,16 @@
"minutes": "{{count}}m ago",
"hours": "{{count}}h ago",
"days": "{{count}}d ago"
},
"row_delete_aria": "Delete chat session",
"row_delete_tooltip": "Delete",
"delete_dialog": {
"title": "Delete chat session",
"description_with_title": "\"{{title}}\" and its messages will be permanently removed. This action cannot be undone.",
"description_default": "This chat session and its messages will be permanently removed. This action cannot be undone.",
"cancel": "Cancel",
"confirm": "Delete",
"confirming": "Deleting..."
}
},
"window": {
@@ -46,7 +56,9 @@
"untitled": "New chat",
"my_agents": "My agents",
"others": "Others",
"no_agents": "No agents"
"no_agents": "No agents",
"history_show_tooltip": "Chat history",
"history_back_tooltip": "Back to chat"
},
"empty_state": {
"first_time_title": "Chat with your agents",

View File

@@ -61,6 +61,20 @@
"toast_project_deleted": "Project deleted",
"toast_move_issue_failed": "Failed to move issue"
},
"resources": {
"section_header": "Resources",
"empty": "No resources attached.",
"add_button": "Add resource",
"popover_title": "Attach a GitHub repo",
"attached_badge": "attached",
"remove_tooltip": "Remove",
"url_placeholder": "https://github.com/owner/repo",
"url_submit": "Add",
"toast_attached": "Repository attached",
"toast_attach_failed": "Failed to attach",
"toast_removed": "Resource removed",
"toast_remove_failed": "Failed to remove resource"
},
"delete_dialog": {
"title": "Delete project",
"description": "This will delete the project. Issues will not be deleted but will be unlinked.",

View File

@@ -29,6 +29,16 @@
"minutes": "{{count}} 分钟前",
"hours": "{{count}} 小时前",
"days": "{{count}} 天前"
},
"row_delete_aria": "删除对话",
"row_delete_tooltip": "删除",
"delete_dialog": {
"title": "删除对话",
"description_with_title": "\"{{title}}\" 及其消息会被永久删除,无法撤销。",
"description_default": "此对话及其消息会被永久删除,无法撤销。",
"cancel": "取消",
"confirm": "删除",
"confirming": "删除中..."
}
},
"window": {
@@ -44,7 +54,9 @@
"untitled": "新对话",
"my_agents": "我的智能体",
"others": "其他",
"no_agents": "暂无智能体"
"no_agents": "暂无智能体",
"history_show_tooltip": "对话历史",
"history_back_tooltip": "返回对话"
},
"empty_state": {
"first_time_title": "和你的智能体对话",

View File

@@ -61,6 +61,20 @@
"toast_project_deleted": "已删除项目",
"toast_move_issue_failed": "移动 issue 失败"
},
"resources": {
"section_header": "资源",
"empty": "还没有关联资源。",
"add_button": "添加资源",
"popover_title": "关联 GitHub 仓库",
"attached_badge": "已关联",
"remove_tooltip": "移除",
"url_placeholder": "https://github.com/owner/repo",
"url_submit": "添加",
"toast_attached": "已关联仓库",
"toast_attach_failed": "关联失败",
"toast_removed": "已移除资源",
"toast_remove_failed": "移除资源失败"
},
"delete_dialog": {
"title": "删除项目",
"description": "将删除这个项目。issue 不会被删除,只会从项目中解除关联。",

View File

@@ -26,6 +26,7 @@ import {
TooltipTrigger,
TooltipContent,
} from "@multica/ui/components/ui/tooltip";
import { useT } from "../../i18n";
// Project Resources sidebar section.
//
@@ -33,6 +34,7 @@ import {
// so adding a new type means: (1) extend the API validator, (2) add a render
// case here. No changes to the schema or query layer.
export function ProjectResourcesSection({ projectId }: { projectId: string }) {
const { t } = useT("projects");
const wsId = useWorkspaceId();
const workspace = useCurrentWorkspace();
const [open, setOpen] = useState(true);
@@ -56,9 +58,9 @@ export function ProjectResourcesSection({ projectId }: { projectId: string }) {
resource_type: "github_repo",
resource_ref: { url },
});
toast.success("Repository attached");
toast.success(t(($) => $.resources.toast_attached));
} catch (err) {
const msg = err instanceof Error ? err.message : "Failed to attach";
const msg = err instanceof Error ? err.message : t(($) => $.resources.toast_attach_failed);
toast.error(msg);
}
};
@@ -66,9 +68,9 @@ export function ProjectResourcesSection({ projectId }: { projectId: string }) {
const handleRemove = async (resource: ProjectResource) => {
try {
await deleteResource.mutateAsync(resource.id);
toast.success("Resource removed");
toast.success(t(($) => $.resources.toast_removed));
} catch {
toast.error("Failed to remove resource");
toast.error(t(($) => $.resources.toast_remove_failed));
}
};
@@ -78,7 +80,7 @@ export function ProjectResourcesSection({ projectId }: { projectId: string }) {
className={`flex w-full items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors mb-2 hover:bg-accent/70 ${open ? "" : "text-muted-foreground hover:text-foreground"}`}
onClick={() => setOpen(!open)}
>
Resources
{t(($) => $.resources.section_header)}
<ChevronRight
className={`!size-3 shrink-0 stroke-[2.5] text-muted-foreground transition-transform ${open ? "rotate-90" : ""}`}
/>
@@ -87,7 +89,7 @@ export function ProjectResourcesSection({ projectId }: { projectId: string }) {
<div className="pl-2 space-y-1.5">
{resources.length === 0 && (
<p className="text-xs text-muted-foreground">
No resources attached.
{t(($) => $.resources.empty)}
</p>
)}
{resources.map((resource) => (
@@ -106,13 +108,13 @@ export function ProjectResourcesSection({ projectId }: { projectId: string }) {
className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground"
>
<Plus className="size-3" />
Add resource
{t(($) => $.resources.add_button)}
</Button>
}
/>
<PopoverContent align="start" className="w-72 p-2 space-y-2">
<div className="text-xs font-medium text-muted-foreground">
Attach a GitHub repo
{t(($) => $.resources.popover_title)}
</div>
{workspace?.repos && workspace.repos.length > 0 && (
<div className="space-y-1">
@@ -145,7 +147,7 @@ export function ProjectResourcesSection({ projectId }: { projectId: string }) {
</Tooltip>
{isAttached && (
<span className="text-[10px] text-muted-foreground">
attached
{t(($) => $.resources.attached_badge)}
</span>
)}
</button>
@@ -174,6 +176,7 @@ function ResourceRow({
resource: ProjectResource;
onRemove: () => void;
}) {
const { t } = useT("projects");
if (resource.resource_type === "github_repo") {
const ref = resource.resource_ref as GithubRepoResourceRef;
return (
@@ -198,7 +201,7 @@ function ResourceRow({
type="button"
onClick={onRemove}
className="opacity-0 group-hover:opacity-100 transition-opacity rounded-sm p-0.5 hover:bg-accent"
title="Remove"
title={t(($) => $.resources.remove_tooltip)}
>
<Trash2 className="size-3 text-muted-foreground" />
</button>
@@ -214,7 +217,7 @@ function ResourceRow({
type="button"
onClick={onRemove}
className="rounded-sm p-0.5 hover:bg-accent"
title="Remove"
title={t(($) => $.resources.remove_tooltip)}
>
<Trash2 className="size-3" />
</button>
@@ -227,6 +230,7 @@ function CustomRepoForm({
}: {
onSubmit: (url: string) => Promise<void> | void;
}) {
const { t } = useT("projects");
const [url, setUrl] = useState("");
const [submitting, setSubmitting] = useState(false);
const handle = async (e: React.FormEvent) => {
@@ -247,7 +251,7 @@ function CustomRepoForm({
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://github.com/owner/repo"
placeholder={t(($) => $.resources.url_placeholder)}
className="flex-1 bg-transparent text-xs px-2 py-1 outline-none placeholder:text-muted-foreground"
/>
<Button
@@ -257,7 +261,7 @@ function CustomRepoForm({
className="h-6 px-2 text-xs"
disabled={!url.trim() || submitting}
>
Add
{t(($) => $.resources.url_submit)}
</Button>
</form>
);