继续多语言补充

This commit is contained in:
xintaofei
2026-03-07 14:22:18 +08:00
parent 47189318e5
commit 89c91ac1eb
12 changed files with 398 additions and 117 deletions

View File

@@ -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 (
<Button
aria-label="Previous branch"
aria-label={t("previousBranchAria")}
disabled={totalBranches <= 1}
onClick={goToPrevious}
size="icon-sm"
@@ -281,11 +283,12 @@ export const MessageBranchNext = ({
children,
...props
}: MessageBranchNextProps) => {
const t = useTranslations("Folder.chat.messageBranch")
const { goToNext, totalBranches } = useMessageBranch()
return (
<Button
aria-label="Next branch"
aria-label={t("nextBranchAria")}
disabled={totalBranches <= 1}
onClick={goToNext}
size="icon-sm"
@@ -304,6 +307,7 @@ export const MessageBranchPage = ({
className,
...props
}: MessageBranchPageProps) => {
const t = useTranslations("Folder.chat.messageBranch")
const { currentBranch, totalBranches } = useMessageBranch()
return (
@@ -314,7 +318,7 @@ export const MessageBranchPage = ({
)}
{...props}
>
{currentBranch + 1} of {totalBranches}
{t("pageOf", { current: currentBranch + 1, total: totalBranches })}
</ButtonGroupText>
)
}

View File

@@ -3,6 +3,7 @@
import type { ComponentProps, ReactNode } from "react"
import { useControllableState } from "@radix-ui/react-use-controllable-state"
import { useTranslations } from "next-intl"
import {
Collapsible,
CollapsibleContent,
@@ -155,24 +156,29 @@ export type ReasoningTriggerProps = ComponentProps<
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode
}
const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
if (isStreaming || duration === 0) {
return <Shimmer duration={1}>Thinking...</Shimmer>
}
if (duration === undefined) {
return <p>Thought for a few seconds</p>
}
return <p>Thought for {duration} seconds</p>
}
export const ReasoningTrigger = memo(
({
className,
children,
getThinkingMessage = defaultGetThinkingMessage,
getThinkingMessage,
...props
}: ReasoningTriggerProps) => {
const t = useTranslations("Folder.chat.reasoning")
const { isStreaming, isOpen, duration } = useReasoning()
const defaultGetThinkingMessage = useCallback(
(nextIsStreaming: boolean, nextDuration?: number) => {
if (nextIsStreaming || nextDuration === 0) {
return <Shimmer duration={1}>{t("thinking")}</Shimmer>
}
if (nextDuration === undefined) {
return <p>{t("thoughtForFewSeconds")}</p>
}
return <p>{t("thoughtForSeconds", { duration: nextDuration })}</p>
},
[t]
)
const thinkingMessageBuilder =
getThinkingMessage ?? defaultGetThinkingMessage
return (
<CollapsibleTrigger
@@ -185,7 +191,7 @@ export const ReasoningTrigger = memo(
{children ?? (
<>
<BrainIcon className="size-4" />
{getThinkingMessage(isStreaming, duration)}
{thinkingMessageBuilder(isStreaming, duration)}
<ChevronDownIcon
className={cn(
"size-4 transition-transform",

View File

@@ -3,6 +3,7 @@
import type { ComponentProps, HTMLAttributes } from "react"
import Ansi from "ansi-to-react"
import { CheckIcon, CopyIcon, TerminalIcon, Trash2Icon } from "lucide-react"
import { useTranslations } from "next-intl"
import {
createContext,
useCallback,
@@ -127,6 +128,7 @@ export function TerminalTitle({
children,
...props
}: TerminalTitleProps) {
const t = useTranslations("Folder.chat.terminal")
return (
<div
className={cn(
@@ -136,7 +138,7 @@ export function TerminalTitle({
{...props}
>
<TerminalIcon className="size-4" />
{children ?? "Terminal"}
{children ?? t("title")}
</div>
)
}
@@ -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 ?? <Shimmer>Running</Shimmer>}
{children ?? <Shimmer>{t("running")}</Shimmer>}
</div>
)
}

View File

@@ -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<ToolPart["state"], string> = {
"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<ToolPart["state"], ReactNode> = {
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
@@ -67,10 +58,10 @@ const statusIcons: Record<ToolPart["state"], ReactNode> = {
"output-error": <XCircleIcon className="size-4 text-red-600" />,
}
export const getStatusBadge = (status: ToolPart["state"]) => (
export const getStatusBadge = (status: ToolPart["state"], label: string) => (
<Badge className="gap-1.5 rounded-full text-xs" variant="secondary">
{statusIcons[status]}
{statusLabels[status]}
{label}
</Badge>
)
@@ -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 (
<CollapsibleTrigger
@@ -103,7 +109,7 @@ export const ToolHeader = ({
{title ?? derivedName}
</span>
{titleSuffix ? <span className="shrink-0">{titleSuffix}</span> : null}
<span className="shrink-0">{getStatusBadge(state)}</span>
<span className="shrink-0">{getStatusBadge(state, statusLabel)}</span>
</div>
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</CollapsibleTrigger>
@@ -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 (
<div className={cn("space-y-2 overflow-hidden", className)} {...props}>
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
Parameters
{t("parameters")}
</h4>
<div className="rounded-md bg-muted/50">
<CodeBlock code={formattedCode} language="json" />