i18n: add missing translations for chat UI components (#457)

* i18n: add missing translations for chat UI components

* i18n: add missing translations for chat components and toast messages
This commit is contained in:
broBinChen
2025-12-30 19:52:57 +08:00
committed by GitHub
parent 1ab8d260a2
commit ad80e9c6f5
6 changed files with 122 additions and 55 deletions

View File

@@ -27,6 +27,7 @@ import {
ReasoningTrigger,
} from "@/components/ai-elements/reasoning"
import { ScrollArea } from "@/components/ui/scroll-area"
import { useDictionary } from "@/hooks/use-dictionary"
import { getApiEndpoint } from "@/lib/base-path"
import {
applyDiagramOperations,
@@ -205,6 +206,7 @@ export function ChatMessageDisplay({
onEditMessage,
status = "idle",
}: ChatMessageDisplayProps) {
const dict = useDictionary()
const { chartXML, loadDiagram: onDisplayChart } = useDiagram()
const messagesEndRef = useRef<HTMLDivElement>(null)
const previousXML = useRef<string>("")
@@ -268,9 +270,7 @@ export function ChatMessageDisplay({
setTimeout(() => setCopiedMessageId(null), 2000)
} catch (fallbackErr) {
console.error("Failed to copy message:", fallbackErr)
toast.error(
"Failed to copy message. Please copy manually or check clipboard permissions.",
)
toast.error(dict.chat.failedToCopyDetail)
setCopyFailedMessageId(messageId)
setTimeout(() => setCopyFailedMessageId(null), 2000)
} finally {
@@ -304,7 +304,7 @@ export function ChatMessageDisplay({
})
} catch (error) {
console.error("Failed to log feedback:", error)
toast.error("Failed to record your feedback. Please try again.")
toast.error(dict.errors.failedToRecordFeedback)
// Revert optimistic UI update
setFeedback((prev) => {
const next = { ...prev }
@@ -349,9 +349,7 @@ export function ChatMessageDisplay({
console.error(
"[ChatMessageDisplay] Malformed XML detected in final output",
)
toast.error(
"AI generated invalid diagram XML. Please try regenerating.",
)
toast.error(dict.errors.malformedXml)
}
return // Skip this update
}
@@ -402,9 +400,7 @@ export function ChatMessageDisplay({
"[ChatMessageDisplay] XML validation failed:",
validation.error,
)
toast.error(
"Diagram validation failed. Please try regenerating.",
)
toast.error(dict.errors.validationFailed)
}
} catch (error) {
console.error(
@@ -413,9 +409,7 @@ export function ChatMessageDisplay({
)
// Only show toast if this is the final XML (not during streaming)
if (showToast) {
toast.error(
"Failed to process diagram. Please try regenerating.",
)
toast.error(dict.errors.failedToProcess)
}
}
}
@@ -832,7 +826,10 @@ export function ChatMessageDisplay({
)
}}
className="p-1.5 rounded-lg text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted transition-colors"
title="Edit message"
title={
dict.chat
.editMessage
}
>
<Pencil className="h-3.5 w-3.5" />
</button>
@@ -849,11 +846,13 @@ export function ChatMessageDisplay({
title={
copiedMessageId ===
message.id
? "Copied!"
? dict.chat.copied
: copyFailedMessageId ===
message.id
? "Failed to copy"
: "Copy message"
? dict.chat
.failedToCopy
: dict.chat
.copyResponse
}
>
{copiedMessageId ===
@@ -968,7 +967,7 @@ export function ChatMessageDisplay({
}}
className="px-3 py-1.5 text-xs rounded-lg bg-muted hover:bg-muted/80 transition-colors"
>
Cancel
{dict.common.cancel}
</button>
<button
type="button"
@@ -990,7 +989,7 @@ export function ChatMessageDisplay({
disabled={!editText.trim()}
className="px-3 py-1.5 text-xs rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
>
Save & Submit
{dict.chat.saveAndSubmit}
</button>
</div>
</div>
@@ -1123,7 +1122,8 @@ export function ChatMessageDisplay({
"user" &&
isLastUserMessage &&
onEditMessage
? "Click to edit"
? dict.chat
.clickToEdit
: undefined
}
>
@@ -1325,8 +1325,8 @@ export function ChatMessageDisplay({
title={
copiedMessageId ===
message.id
? "Copied!"
: "Copy response"
? dict.chat.copied
: dict.chat.copyResponse
}
>
{copiedMessageId ===
@@ -1352,7 +1352,9 @@ export function ChatMessageDisplay({
)
}
className="p-1.5 rounded-lg text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors"
title="Regenerate response"
title={
dict.chat.regenerate
}
>
<RotateCcw className="h-3.5 w-3.5" />
</button>
@@ -1374,7 +1376,7 @@ export function ChatMessageDisplay({
? "text-green-600 bg-green-100"
: "text-muted-foreground/60 hover:text-green-600 hover:bg-green-50"
}`}
title="Good response"
title={dict.chat.goodResponse}
>
<ThumbsUp className="h-3.5 w-3.5" />
</button>
@@ -1393,7 +1395,7 @@ export function ChatMessageDisplay({
? "text-red-600 bg-red-100"
: "text-muted-foreground/60 hover:text-red-600 hover:bg-red-50"
}`}
title="Bad response"
title={dict.chat.badResponse}
>
<ThumbsDown className="h-3.5 w-3.5" />
</button>

View File

@@ -26,6 +26,7 @@ import { useDictionary } from "@/hooks/use-dictionary"
import { getSelectedAIConfig, useModelConfig } from "@/hooks/use-model-config"
import { getApiEndpoint } from "@/lib/base-path"
import { findCachedResponse } from "@/lib/cached-responses"
import { formatMessage } from "@/lib/i18n/utils"
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
import { type FileData, useFileProcessor } from "@/lib/use-file-processor"
import { useQuotaManager } from "@/lib/use-quota-manager"
@@ -389,7 +390,9 @@ export default function ChatPanel({
MAX_CONTINUATION_RETRY_COUNT
) {
toast.error(
`Continuation retry limit reached (${MAX_CONTINUATION_RETRY_COUNT}). The diagram may be too complex.`,
formatMessage(dict.errors.continuationRetryLimit, {
max: MAX_CONTINUATION_RETRY_COUNT,
}),
)
continuationRetryCountRef.current = 0
partialXmlRef.current = ""
@@ -400,7 +403,9 @@ export default function ChatPanel({
// Regular error: check retry count limit
if (autoRetryCountRef.current >= MAX_AUTO_RETRY_COUNT) {
toast.error(
`Auto-retry limit reached (${MAX_AUTO_RETRY_COUNT}). Please try again manually.`,
formatMessage(dict.errors.retryLimit, {
max: MAX_AUTO_RETRY_COUNT,
}),
)
autoRetryCountRef.current = 0
partialXmlRef.current = ""
@@ -450,7 +455,7 @@ export default function ChatPanel({
// On complete failure, clear storage to allow recovery
localStorage.removeItem(STORAGE_MESSAGES_KEY)
localStorage.removeItem(STORAGE_XML_SNAPSHOTS_KEY)
toast.error("Session data was corrupted. Starting fresh.")
toast.error(dict.errors.sessionCorrupted)
}
}, [setMessages])
@@ -651,12 +656,10 @@ export default function ChatPanel({
localStorage.removeItem(STORAGE_DIAGRAM_XML_KEY)
localStorage.setItem(STORAGE_SESSION_ID_KEY, newSessionId)
sessionStorage.removeItem(SESSION_STORAGE_INPUT_KEY)
toast.success("Started a fresh chat")
toast.success(dict.dialogs.clearSuccess)
} catch (error) {
console.error("Failed to clear localStorage:", error)
toast.warning(
"Chat cleared but browser storage could not be updated",
)
toast.warning(dict.errors.storageUpdateFailed)
}
setShowNewChatDialog(false)
@@ -889,7 +892,7 @@ export default function ChatPanel({
return (
<div className="h-full flex flex-col items-center pt-4 bg-card border border-border/30 rounded-xl">
<ButtonWithTooltip
tooltipContent="Show chat panel (Ctrl+B)"
tooltipContent={dict.nav.showPanel}
variant="ghost"
size="icon"
onClick={onToggleVisibility}
@@ -904,7 +907,7 @@ export default function ChatPanel({
transform: "rotate(180deg)",
}}
>
AI Chat
{dict.nav.aiChat}
</div>
</div>
)
@@ -956,7 +959,7 @@ export default function ChatPanel({
rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground transition-colors ml-2"
>
About
{dict.nav.about}
</Link>
)}
{!isMobile && (
@@ -966,7 +969,7 @@ export default function ChatPanel({
rel="noopener noreferrer"
>
<ButtonWithTooltip
tooltipContent="Sponsored by ByteDance Doubao K2-thinking. See About page for details."
tooltipContent={dict.nav.sponsorTooltip}
variant="ghost"
size="icon"
className="h-6 w-6 text-amber-500 hover:text-amber-600"

View File

@@ -1,6 +1,7 @@
"use client"
import { useEffect, useRef, useState } from "react"
import { useDictionary } from "@/hooks/use-dictionary"
import { wrapWithMxFile } from "@/lib/utils"
// Dev XML presets for streaming simulator
@@ -142,6 +143,7 @@ export function DevXmlSimulator({
onDisplayChart,
onShowQuotaToast,
}: DevXmlSimulatorProps) {
const dict = useDictionary()
const [devXml, setDevXml] = useState("")
const [isSimulating, setIsSimulating] = useState(false)
const [devIntervalMs, setDevIntervalMs] = useState(1)
@@ -178,7 +180,7 @@ export function DevXmlSimulator({
parts: [
{
type: "text" as const,
text: "[Dev] Simulating XML streaming",
text: dict.dev.simulatingMessage,
},
],
}
@@ -228,7 +230,7 @@ export function DevXmlSimulator({
const lastMsg = updated[updated.length - 1] as any
if (lastMsg?.role === "assistant" && lastMsg.parts?.[0]) {
lastMsg.parts[0].state = "output-available"
lastMsg.parts[0].output = "Successfully displayed the diagram."
lastMsg.parts[0].output = dict.dev.successMessage
lastMsg.parts[0].input = { xml }
}
return updated
@@ -245,12 +247,12 @@ export function DevXmlSimulator({
<div className="border-t border-dashed border-orange-500/50 px-4 py-2 bg-orange-50/50 dark:bg-orange-950/30">
<details>
<summary className="text-xs text-orange-600 dark:text-orange-400 cursor-pointer font-medium">
Dev: XML Streaming Simulator
{dict.dev.title}
</summary>
<div className="mt-2 space-y-2">
<div className="flex items-center gap-2">
<label className="text-xs text-muted-foreground whitespace-nowrap">
Preset:
{dict.dev.preset}
</label>
<select
onChange={(e) => {
@@ -262,7 +264,7 @@ export function DevXmlSimulator({
defaultValue=""
>
<option value="" disabled>
Select a preset...
{dict.dev.selectPreset}
</option>
{Object.keys(DEV_XML_PRESETS).map((name) => (
<option key={name} value={name}>
@@ -275,19 +277,19 @@ export function DevXmlSimulator({
onClick={() => setDevXml("")}
className="px-2 py-1 text-xs text-muted-foreground hover:text-foreground border rounded"
>
Clear
{dict.dev.clear}
</button>
</div>
<textarea
value={devXml}
onChange={(e) => setDevXml(e.target.value)}
placeholder="Paste mxCell XML here or select a preset..."
placeholder={dict.dev.placeholder}
className="w-full h-24 text-xs font-mono p-2 border rounded bg-background"
/>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 flex-1">
<label className="text-xs text-muted-foreground whitespace-nowrap">
Interval:
{dict.dev.interval}
</label>
<input
type="range"
@@ -306,7 +308,7 @@ export function DevXmlSimulator({
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-muted-foreground whitespace-nowrap">
Chars:
{dict.dev.chars}
</label>
<input
type="number"
@@ -330,8 +332,8 @@ export function DevXmlSimulator({
className="px-3 py-1 text-xs bg-orange-500 text-white rounded hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSimulating
? "Streaming..."
: `Simulate (${devChunkSize} chars/${devIntervalMs}ms)`}
? dict.dev.streaming
: `${dict.dev.simulate} (${devChunkSize} chars/${devIntervalMs}ms)`}
</button>
{isSimulating && (
<button
@@ -341,7 +343,7 @@ export function DevXmlSimulator({
}}
className="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600"
>
Stop
{dict.dev.stop}
</button>
)}
{onShowQuotaToast && (
@@ -350,7 +352,7 @@ export function DevXmlSimulator({
onClick={onShowQuotaToast}
className="px-3 py-1 text-xs bg-purple-500 text-white rounded hover:bg-purple-600"
>
Test Quota Toast
{dict.dev.testQuotaToast}
</button>
)}
</div>