From 64f704ad543f3fce187e71626874d1e455cb12d7 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 30 Jan 2026 04:49:38 +0000 Subject: [PATCH] fix: resolve React Compiler i18n caching issues in ChatView Fixes partial internationalization failures caused by React Compiler aggressively caching translated values. Changes: - TranslationContext: Add i18n.language to translate callback dependency to force re-memoization when language changes - ChatView: Store translation keys instead of translated button text in state, compute translations at render time - ChatView: Remove redundant useState type annotations that may confuse React Compiler type inference - ChatView: Simplify button tooltip rendering by using pre-computed tooltip values from key mappings Closes #11100 --- webview-ui/src/components/chat/ChatView.tsx | 195 ++++++++++++-------- webview-ui/src/i18n/TranslationContext.tsx | 5 +- 2 files changed, 117 insertions(+), 83 deletions(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 6e9bea03893..11c23222980 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -62,6 +62,50 @@ export interface ChatViewRef { export const MAX_IMAGES_PER_MESSAGE = 20 // This is the Anthropic limit. +// Button text keys - store translation keys instead of translated values +// This ensures React Compiler doesn't cache stale translations +type PrimaryButtonKey = + | "chat:retry.title" + | "chat:proceedAnyways.title" + | "chat:save.title" + | "chat:completeSubtaskAndReturn" + | "chat:read-batch.approve.title" + | "chat:approve.title" + | "chat:runCommand.title" + | "chat:proceedWhileRunning.title" + | "chat:startNewTask.title" + | "chat:resumeTask.title" + +type SecondaryButtonKey = + | "chat:startNewTask.title" + | "chat:reject.title" + | "chat:read-batch.deny.title" + | "chat:terminate.title" + | "chat:killCommand.title" + +// Map primary button keys to their tooltip keys +const primaryButtonTooltipMap: Record = { + "chat:retry.title": "chat:retry.tooltip", + "chat:proceedAnyways.title": "chat:proceedAnyways.tooltip", + "chat:save.title": "chat:save.tooltip", + "chat:completeSubtaskAndReturn": undefined, + "chat:read-batch.approve.title": undefined, + "chat:approve.title": "chat:approve.tooltip", + "chat:runCommand.title": "chat:runCommand.tooltip", + "chat:proceedWhileRunning.title": "chat:proceedWhileRunning.tooltip", + "chat:startNewTask.title": "chat:startNewTask.tooltip", + "chat:resumeTask.title": "chat:resumeTask.tooltip", +} + +// Map secondary button keys to their tooltip keys +const secondaryButtonTooltipMap: Record = { + "chat:startNewTask.title": "chat:startNewTask.tooltip", + "chat:reject.title": "chat:reject.tooltip", + "chat:read-batch.deny.title": undefined, + "chat:terminate.title": "chat:terminate.tooltip", + "chat:killCommand.title": "chat:killCommand.tooltip", +} + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0 const ChatViewComponent: React.ForwardRefRenderFunction = ( @@ -142,9 +186,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction(undefined) - const [enableButtons, setEnableButtons] = useState(false) - const [primaryButtonText, setPrimaryButtonText] = useState(undefined) - const [secondaryButtonText, setSecondaryButtonText] = useState(undefined) + // Remove redundant type annotation - React Compiler works better with inferred types + const [enableButtons, setEnableButtons] = useState(false) + // Store translation keys instead of translated values to avoid React Compiler caching issues + const [primaryButtonKey, setPrimaryButtonKey] = useState(undefined) + const [secondaryButtonKey, setSecondaryButtonKey] = useState(undefined) const [_didClickCancel, setDidClickCancel] = useState(false) const virtuosoRef = useRef(null) const [expandedRows, setExpandedRows] = useState>({}) @@ -154,11 +200,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction("") - const [wasStreaming, setWasStreaming] = useState(false) + // Remove redundant type annotation - React Compiler works better with inferred types + const [wasStreaming, setWasStreaming] = useState(false) const [checkpointWarning, setCheckpointWarning] = useState< { type: "WAIT_TIMEOUT" | "INIT_TIMEOUT"; timeout: number } | undefined >(undefined) - const [isCondensing, setIsCondensing] = useState(false) + // Remove redundant type annotation - React Compiler works better with inferred types + const [isCondensing, setIsCondensing] = useState(false) const [showAnnouncementModal, setShowAnnouncementModal] = useState(false) const everVisibleMessagesTsRef = useRef>( new LRUCache({ @@ -180,6 +228,21 @@ const ChatViewComponent: React.ForwardRefRenderFunction >(new Map()) + // Compute translated button text and tooltips at render time + // This ensures translations update correctly when language changes + const primaryButtonText = primaryButtonKey ? t(primaryButtonKey) : undefined + const secondaryButtonText = secondaryButtonKey ? t(secondaryButtonKey) : undefined + const primaryButtonTooltip = primaryButtonKey + ? primaryButtonTooltipMap[primaryButtonKey] + ? t(primaryButtonTooltipMap[primaryButtonKey]!) + : undefined + : undefined + const secondaryButtonTooltip = secondaryButtonKey + ? secondaryButtonTooltipMap[secondaryButtonKey] + ? t(secondaryButtonTooltipMap[secondaryButtonKey]!) + : undefined + : undefined + const clineAskRef = useRef(clineAsk) useEffect(() => { clineAskRef.current = clineAsk @@ -280,16 +343,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction msg.ask === "completion_result" || msg.say === "completion_result", ) if (isCompletedSubtask) { - setPrimaryButtonText(t("chat:startNewTask.title")) - setSecondaryButtonText(undefined) + setPrimaryButtonKey("chat:startNewTask.title") + setSecondaryButtonKey(undefined) } else { - setPrimaryButtonText(t("chat:resumeTask.title")) - setSecondaryButtonText(t("chat:terminate.title")) + setPrimaryButtonKey("chat:resumeTask.title") + setSecondaryButtonKey("chat:terminate.title") } setDidClickCancel(false) // special case where we reset the cancel button state break @@ -400,8 +463,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction msg.ask === "completion_result" || msg.say === "completion_result", ) if (hasCompletionResult) { - setPrimaryButtonText(t("chat:startNewTask.title")) - setSecondaryButtonText(undefined) + setPrimaryButtonKey("chat:startNewTask.title") + setSecondaryButtonKey(undefined) } } - }, [clineAsk, currentTaskItem?.parentTaskId, messages, t]) + }, [clineAsk, currentTaskItem?.parentTaskId, messages]) useEffect(() => { if (messages.length === 0) { setSendingDisabled(false) setClineAsk(undefined) setEnableButtons(false) - setPrimaryButtonText(undefined) - setSecondaryButtonText(undefined) + setPrimaryButtonKey(undefined) + setSecondaryButtonKey(undefined) } }, [messages.length]) @@ -594,8 +657,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction {primaryButtonText && ( - +