Browse Source

Use Lucide icons and translations in the code block (#3203)

Matt Rubens 7 months ago
parent
commit
8ab0de3d02

+ 16 - 15
webview-ui/src/components/common/CodeBlock.tsx

@@ -4,6 +4,8 @@ import { useCopyToClipboard } from "@src/utils/clipboard"
 import { getHighlighter, isLanguageLoaded, normalizeLanguage, ExtendedLanguage } from "@src/utils/highlighter"
 import { bundledLanguages } from "shiki"
 import type { ShikiTransformer } from "shiki"
+import { ChevronDown, ChevronUp, WrapText, AlignJustify, Copy, Check } from "lucide-react"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
 export const CODE_BLOCK_BG_COLOR = "var(--vscode-editor-background, --vscode-sideBar-background, rgb(30 30 30))"
 export const WRAPPER_ALPHA = "cc" // 80% opacity
 // Configuration constants
@@ -34,13 +36,6 @@ interface CodeBlockProps {
 	onLanguageChange?: (language: string) => void
 }
 
-const ButtonIcon = styled.span`
-	display: inline-block;
-	width: 1.2em;
-	text-align: center;
-	vertical-align: middle;
-`
-
 const CodeBlockButton = styled.button`
 	background: transparent;
 	border: none;
@@ -50,16 +45,23 @@ const CodeBlockButton = styled.button`
 	margin: 0 0px;
 	display: flex;
 	align-items: center;
+	justify-content: center;
 	opacity: 0.4;
 	border-radius: 3px;
 	pointer-events: var(--copy-button-events, none);
 	margin-left: 4px;
 	height: 24px;
+	width: 24px;
 
 	&:hover {
 		background: var(--vscode-toolbar-hoverBackground);
 		opacity: 1;
 	}
+
+	/* Style for Lucide icons to ensure consistent sizing and positioning */
+	svg {
+		display: block;
+	}
 `
 
 const CodeBlockButtonWrapper = styled.div`
@@ -229,6 +231,7 @@ const CodeBlock = memo(
 		const preRef = useRef<HTMLDivElement>(null)
 		const copyButtonWrapperRef = useRef<HTMLDivElement>(null)
 		const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
+		const { t } = useAppTranslation()
 
 		// Update current language when prop changes, but only if user hasn't made a selection
 		useEffect(() => {
@@ -696,19 +699,17 @@ const CodeBlock = memo(
 										WINDOW_SHADE_SETTINGS.transitionDelayS * 1000 + 50,
 									)
 								}}
-								title={`${windowShade ? "Expand" : "Collapse"} code block`}>
-								<ButtonIcon style={{ fontSize: "16px" }}>{windowShade ? "⌄" : "⌃"}</ButtonIcon>
+								title={t(`chat:codeblock.tooltips.${windowShade ? "expand" : "collapse"}`)}>
+								{windowShade ? <ChevronDown size={16} /> : <ChevronUp size={16} />}
 							</CodeBlockButton>
 						)}
 						<CodeBlockButton
 							onClick={() => setWordWrap(!wordWrap)}
-							title={`${wordWrap ? "Disable" : "Enable"} word wrap`}>
-							<ButtonIcon style={{ fontSize: "16px", fontWeight: 900 }}>
-								{wordWrap ? "⟼" : "⤸"}
-							</ButtonIcon>
+							title={t(`chat:codeblock.tooltips.${wordWrap ? "disable_wrap" : "enable_wrap"}`)}>
+							{wordWrap ? <AlignJustify size={16} /> : <WrapText size={16} />}
 						</CodeBlockButton>
-						<CodeBlockButton onClick={handleCopy} title="Copy code">
-							<ButtonIcon className={`codicon codicon-${showCopyFeedback ? "check" : "copy"}`} />
+						<CodeBlockButton onClick={handleCopy} title={t("chat:codeblock.tooltips.copy_code")}>
+							{showCopyFeedback ? <Check size={16} /> : <Copy size={16} />}
 						</CodeBlockButton>
 					</CodeBlockButtonWrapper>
 				)}

+ 33 - 0
webview-ui/src/components/common/__tests__/CodeBlock.test.tsx

@@ -2,6 +2,23 @@ import { render, screen, fireEvent, act } from "@testing-library/react"
 import "@testing-library/jest-dom"
 import CodeBlock from "../CodeBlock"
 
+// Mock the translation context
+jest.mock("../../../i18n/TranslationContext", () => ({
+	useAppTranslation: () => ({
+		t: (key: string) => {
+			// Return fixed English strings for tests
+			const translations: { [key: string]: string } = {
+				"chat:codeblock.tooltips.copy_code": "Copy code",
+				"chat:codeblock.tooltips.expand": "Expand code block",
+				"chat:codeblock.tooltips.collapse": "Collapse code block",
+				"chat:codeblock.tooltips.enable_wrap": "Enable word wrap",
+				"chat:codeblock.tooltips.disable_wrap": "Disable word wrap",
+			}
+			return translations[key] || key
+		},
+	}),
+}))
+
 // Mock shiki module
 jest.mock("shiki", () => ({
 	bundledLanguages: {
@@ -11,6 +28,22 @@ jest.mock("shiki", () => ({
 	},
 }))
 
+// Mock all lucide-react icons with a proxy to handle any icon requested
+jest.mock("lucide-react", () => {
+	return new Proxy(
+		{},
+		{
+			get: function (obj, prop) {
+				// Return a component factory for any icon that's requested
+				if (prop === "__esModule") {
+					return true
+				}
+				return () => <div data-testid={`${String(prop)}-icon`}>{String(prop)}</div>
+			},
+		},
+	)
+})
+
 // Mock the highlighter utility
 jest.mock("../../../utils/highlighter", () => {
 	const mockHighlighter = {

+ 9 - 0
webview-ui/src/i18n/locales/ca/chat.json

@@ -233,6 +233,15 @@
 			"close": "Tancar navegador"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Expandir bloc de codi",
+			"collapse": "Contraure bloc de codi",
+			"enable_wrap": "Activar ajustament de línia",
+			"disable_wrap": "Desactivar ajustament de línia",
+			"copy_code": "Copiar codi"
+		}
+	},
 	"systemPromptWarning": "ADVERTÈNCIA: S'ha activat una substitució personalitzada d'instruccions del sistema. Això pot trencar greument la funcionalitat i causar un comportament impredictible.",
 	"shellIntegration": {
 		"title": "Advertència d'execució d'ordres",

+ 9 - 0
webview-ui/src/i18n/locales/de/chat.json

@@ -233,6 +233,15 @@
 			"close": "Browser schließen"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Code-Block erweitern",
+			"collapse": "Code-Block reduzieren",
+			"enable_wrap": "Zeilenumbruch aktivieren",
+			"disable_wrap": "Zeilenumbruch deaktivieren",
+			"copy_code": "Code kopieren"
+		}
+	},
 	"systemPromptWarning": "WARNUNG: Benutzerdefinierte Systemaufforderung aktiv. Dies kann die Funktionalität erheblich beeinträchtigen und zu unvorhersehbarem Verhalten führen.",
 	"shellIntegration": {
 		"title": "Befehlsausführungswarnung",

+ 9 - 0
webview-ui/src/i18n/locales/en/chat.json

@@ -233,6 +233,15 @@
 			"close": "Close browser"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Expand code block",
+			"collapse": "Collapse code block",
+			"enable_wrap": "Enable word wrap",
+			"disable_wrap": "Disable word wrap",
+			"copy_code": "Copy code"
+		}
+	},
 	"systemPromptWarning": "WARNING: Custom system prompt override active. This can severely break functionality and cause unpredictable behavior.",
 	"shellIntegration": {
 		"title": "Command Execution Warning",

+ 9 - 0
webview-ui/src/i18n/locales/es/chat.json

@@ -233,6 +233,15 @@
 			"close": "Cerrar navegador"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Expandir bloque de código",
+			"collapse": "Contraer bloque de código",
+			"enable_wrap": "Activar ajuste de línea",
+			"disable_wrap": "Desactivar ajuste de línea",
+			"copy_code": "Copiar código"
+		}
+	},
 	"systemPromptWarning": "ADVERTENCIA: Anulación de instrucciones del sistema personalizada activa. Esto puede romper gravemente la funcionalidad y causar un comportamiento impredecible.",
 	"shellIntegration": {
 		"title": "Advertencia de ejecución de comandos",

+ 9 - 0
webview-ui/src/i18n/locales/fr/chat.json

@@ -233,6 +233,15 @@
 			"close": "Fermer le navigateur"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Développer le bloc de code",
+			"collapse": "Réduire le bloc de code",
+			"enable_wrap": "Activer le retour à la ligne",
+			"disable_wrap": "Désactiver le retour à la ligne",
+			"copy_code": "Copier le code"
+		}
+	},
 	"systemPromptWarning": "AVERTISSEMENT : Remplacement d'instructions système personnalisées actif. Cela peut gravement perturber la fonctionnalité et provoquer un comportement imprévisible.",
 	"shellIntegration": {
 		"title": "Avertissement d'exécution de commande",

+ 9 - 0
webview-ui/src/i18n/locales/hi/chat.json

@@ -233,6 +233,15 @@
 			"close": "ब्राउज़र बंद करें"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "कोड ब्लॉक का विस्तार करें",
+			"collapse": "कोड ब्लॉक को संकुचित करें",
+			"enable_wrap": "वर्ड रैप सक्षम करें",
+			"disable_wrap": "वर्ड रैप अक्षम करें",
+			"copy_code": "कोड कॉपी करें"
+		}
+	},
 	"systemPromptWarning": "चेतावनी: कस्टम सिस्टम प्रॉम्प्ट ओवरराइड सक्रिय है। यह कार्यक्षमता को गंभीर रूप से बाधित कर सकता है और अनियमित व्यवहार का कारण बन सकता है.",
 	"shellIntegration": {
 		"title": "कमांड निष्पादन चेतावनी",

+ 9 - 0
webview-ui/src/i18n/locales/it/chat.json

@@ -233,6 +233,15 @@
 			"close": "Chiudi browser"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Espandi blocco di codice",
+			"collapse": "Comprimi blocco di codice",
+			"enable_wrap": "Attiva a capo automatico",
+			"disable_wrap": "Disattiva a capo automatico",
+			"copy_code": "Copia codice"
+		}
+	},
 	"systemPromptWarning": "ATTENZIONE: Sovrascrittura personalizzata delle istruzioni di sistema attiva. Questo può compromettere gravemente le funzionalità e causare comportamenti imprevedibili.",
 	"shellIntegration": {
 		"title": "Avviso di esecuzione comando",

+ 9 - 0
webview-ui/src/i18n/locales/ja/chat.json

@@ -233,6 +233,15 @@
 			"close": "ブラウザを閉じる"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "コードブロックを展開",
+			"collapse": "コードブロックを折りたたむ",
+			"enable_wrap": "折り返しを有効化",
+			"disable_wrap": "折り返しを無効化",
+			"copy_code": "コードをコピー"
+		}
+	},
 	"systemPromptWarning": "警告:カスタムシステムプロンプトの上書きが有効です。これにより機能が深刻に損なわれ、予測不可能な動作が発生する可能性があります。",
 	"shellIntegration": {
 		"title": "コマンド実行警告",

+ 9 - 0
webview-ui/src/i18n/locales/ko/chat.json

@@ -233,6 +233,15 @@
 			"close": "브라우저 닫기"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "코드 블록 확장",
+			"collapse": "코드 블록 축소",
+			"enable_wrap": "자동 줄바꿈 활성화",
+			"disable_wrap": "자동 줄바꿈 비활성화",
+			"copy_code": "코드 복사"
+		}
+	},
 	"systemPromptWarning": "경고: 사용자 정의 시스템 프롬프트 재정의가 활성화되었습니다. 이로 인해 기능이 심각하게 손상되고 예측할 수 없는 동작이 발생할 수 있습니다.",
 	"shellIntegration": {
 		"title": "명령 실행 경고",

+ 9 - 0
webview-ui/src/i18n/locales/pl/chat.json

@@ -233,6 +233,15 @@
 			"close": "Zamknij przeglądarkę"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Rozwiń blok kodu",
+			"collapse": "Zwiń blok kodu",
+			"enable_wrap": "Włącz zawijanie wierszy",
+			"disable_wrap": "Wyłącz zawijanie wierszy",
+			"copy_code": "Kopiuj kod"
+		}
+	},
 	"systemPromptWarning": "OSTRZEŻENIE: Aktywne niestandardowe zastąpienie instrukcji systemowych. Może to poważnie zakłócić funkcjonalność i powodować nieprzewidywalne zachowanie.",
 	"shellIntegration": {
 		"title": "Ostrzeżenie wykonania polecenia",

+ 9 - 0
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -233,6 +233,15 @@
 			"close": "Fechar navegador"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Expandir bloco de código",
+			"collapse": "Recolher bloco de código",
+			"enable_wrap": "Ativar quebra de linha",
+			"disable_wrap": "Desativar quebra de linha",
+			"copy_code": "Copiar código"
+		}
+	},
 	"systemPromptWarning": "AVISO: Substituição personalizada de instrução do sistema ativa. Isso pode comprometer gravemente a funcionalidade e causar comportamento imprevisível.",
 	"shellIntegration": {
 		"title": "Aviso de execução de comando",

+ 9 - 0
webview-ui/src/i18n/locales/ru/chat.json

@@ -233,6 +233,15 @@
 			"close": "Закрыть браузер"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Развернуть блок кода",
+			"collapse": "Свернуть блок кода",
+			"enable_wrap": "Включить перенос строк",
+			"disable_wrap": "Отключить перенос строк",
+			"copy_code": "Копировать код"
+		}
+	},
 	"systemPromptWarning": "ПРЕДУПРЕЖДЕНИЕ: Активна пользовательская системная подсказка. Это может серьезно нарушить работу и вызвать непредсказуемое поведение.",
 	"shellIntegration": {
 		"title": "Предупреждение о выполнении команды",

+ 9 - 0
webview-ui/src/i18n/locales/tr/chat.json

@@ -233,6 +233,15 @@
 			"close": "Tarayıcıyı kapat"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Kod bloğunu genişlet",
+			"collapse": "Kod bloğunu daralt",
+			"enable_wrap": "Satır kaydırmayı etkinleştir",
+			"disable_wrap": "Satır kaydırmayı devre dışı bırak",
+			"copy_code": "Kodu kopyala"
+		}
+	},
 	"systemPromptWarning": "UYARI: Özel sistem komut geçersiz kılma aktif. Bu işlevselliği ciddi şekilde bozabilir ve öngörülemeyen davranışlara neden olabilir.",
 	"shellIntegration": {
 		"title": "Komut Çalıştırma Uyarısı",

+ 9 - 0
webview-ui/src/i18n/locales/vi/chat.json

@@ -233,6 +233,15 @@
 			"close": "Đóng trình duyệt"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "Mở rộng khối mã",
+			"collapse": "Thu gọn khối mã",
+			"enable_wrap": "Bật tự động xuống dòng",
+			"disable_wrap": "Tắt tự động xuống dòng",
+			"copy_code": "Sao chép mã"
+		}
+	},
 	"systemPromptWarning": "CẢNH BÁO: Đã kích hoạt ghi đè lệnh nhắc hệ thống tùy chỉnh. Điều này có thể phá vỡ nghiêm trọng chức năng và gây ra hành vi không thể dự đoán.",
 	"shellIntegration": {
 		"title": "Cảnh báo thực thi lệnh",

+ 9 - 0
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -233,6 +233,15 @@
 			"close": "关闭浏览器"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "展开代码块",
+			"collapse": "收起代码块",
+			"enable_wrap": "启用自动换行",
+			"disable_wrap": "禁用自动换行",
+			"copy_code": "复制代码"
+		}
+	},
 	"systemPromptWarning": "警告:自定义系统提示词覆盖已激活。这可能严重破坏功能并导致不可预测的行为。",
 	"shellIntegration": {
 		"title": "命令执行警告",

+ 9 - 0
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -233,6 +233,15 @@
 			"close": "關閉瀏覽器"
 		}
 	},
+	"codeblock": {
+		"tooltips": {
+			"expand": "展開程式碼區塊",
+			"collapse": "摺疊程式碼區塊",
+			"enable_wrap": "啟用自動換行",
+			"disable_wrap": "停用自動換行",
+			"copy_code": "複製程式碼"
+		}
+	},
 	"systemPromptWarning": "警告:自訂系統提示詞覆蓋已啟用。這可能嚴重破壞功能並導致不可預測的行為。",
 	"shellIntegration": {
 		"title": "命令執行警告",