Browse Source

Translate history preview and view

Matt Rubens 9 months ago
parent
commit
6cdd10251a
27 changed files with 594 additions and 41 deletions
  1. 1 1
      .roomodes
  2. 3 1
      webview-ui/src/components/history/CopyButton.tsx
  3. 6 6
      webview-ui/src/components/history/DeleteTaskDialog.tsx
  4. 18 13
      webview-ui/src/components/history/ExportButton.tsx
  5. 13 8
      webview-ui/src/components/history/HistoryPreview.tsx
  6. 14 12
      webview-ui/src/components/history/HistoryView.tsx
  7. 1 0
      webview-ui/src/components/history/__tests__/HistoryView.test.tsx
  8. 63 0
      webview-ui/src/i18n/__mocks__/TranslationContext.tsx
  9. 25 0
      webview-ui/src/i18n/locales/ar/history.json
  10. 25 0
      webview-ui/src/i18n/locales/ca/history.json
  11. 25 0
      webview-ui/src/i18n/locales/cs/history.json
  12. 25 0
      webview-ui/src/i18n/locales/de/history.json
  13. 25 0
      webview-ui/src/i18n/locales/en/history.json
  14. 25 0
      webview-ui/src/i18n/locales/es/history.json
  15. 25 0
      webview-ui/src/i18n/locales/fr/history.json
  16. 25 0
      webview-ui/src/i18n/locales/hi/history.json
  17. 25 0
      webview-ui/src/i18n/locales/hu/history.json
  18. 25 0
      webview-ui/src/i18n/locales/it/history.json
  19. 25 0
      webview-ui/src/i18n/locales/ja/history.json
  20. 25 0
      webview-ui/src/i18n/locales/ko/history.json
  21. 25 0
      webview-ui/src/i18n/locales/pl/history.json
  22. 25 0
      webview-ui/src/i18n/locales/pt-BR/history.json
  23. 25 0
      webview-ui/src/i18n/locales/pt/history.json
  24. 25 0
      webview-ui/src/i18n/locales/ru/history.json
  25. 25 0
      webview-ui/src/i18n/locales/tr/history.json
  26. 25 0
      webview-ui/src/i18n/locales/zh-CN/history.json
  27. 25 0
      webview-ui/src/i18n/locales/zh-TW/history.json

+ 1 - 1
.roomodes

@@ -22,7 +22,7 @@
       "slug": "translate",
       "name": "Translate",
       "roleDefinition": "You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.",
-      "customInstructions": "When internationalizing and translating content:\n\n# Translation Style and Tone\n- Maintain a direct and concise style that mirrors the tone of the original text\n- Carefully account for colloquialisms and idiomatic expressions in both source and target languages\n- Aim for culturally relevant and meaningful translations rather than literal translations\n- Adapt the formality level to match the original content (whether formal or informal)\n- Preserve the personality and voice of the original content\n- Use natural-sounding language that feels native to speakers of the target language\n\n# Technical Implementation\n- Use namespaces to organize translations logically\n- Handle pluralization using i18next's built-in capabilities\n- Implement proper interpolation for variables using {{variable}} syntax\n- Don't include defaultValue. The `en` translations are the fallback.\n\n# Quality Assurance\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Watch for placeholders and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- Use context-aware translations when the same string has different meanings\n\n# Supported Languages\n- Localize all strings into the following locale files: ar, ca, cs, de, en, es, fr, hi, hu, it, ja, ko, pl, pt, pt-BR, ru, tr, zh-CN, zh-TW",
+      "customInstructions": "When internationalizing and translating content:\n\n# Translation Style and Tone\n- Maintain a direct and concise style that mirrors the tone of the original text\n- Carefully account for colloquialisms and idiomatic expressions in both source and target languages\n- Aim for culturally relevant and meaningful translations rather than literal translations\n- Adapt the formality level to match the original content (whether formal or informal)\n- Preserve the personality and voice of the original content\n- Use natural-sounding language that feels native to speakers of the target language\n- Don't translate the word \"token\" as it means something specific in English that all languages will understand\n\n# Technical Implementation\n- Use namespaces to organize translations logically\n- Handle pluralization using i18next's built-in capabilities\n- Implement proper interpolation for variables using {{variable}} syntax\n- Don't include defaultValue. The `en` translations are the fallback.\n\n# Quality Assurance\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Watch for placeholders and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- Use context-aware translations when the same string has different meanings\n\n# Supported Languages\n- Localize all strings into the following locale files: ar, ca, cs, de, en, es, fr, hi, hu, it, ja, ko, pl, pt, pt-BR, ru, tr, zh-CN, zh-TW",
       "groups": [
         "read",
         [

+ 3 - 1
webview-ui/src/components/history/CopyButton.tsx

@@ -3,6 +3,7 @@ import { useCallback } from "react"
 import { useClipboard } from "@/components/ui/hooks"
 import { Button } from "@/components/ui"
 import { cn } from "@/lib/utils"
+import { useAppTranslation } from "@/i18n/TranslationContext"
 
 type CopyButtonProps = {
 	itemTask: string
@@ -10,6 +11,7 @@ type CopyButtonProps = {
 
 export const CopyButton = ({ itemTask }: CopyButtonProps) => {
 	const { isCopied, copy } = useClipboard()
+	const { t } = useAppTranslation()
 
 	const onCopy = useCallback(
 		(e: React.MouseEvent) => {
@@ -23,7 +25,7 @@ export const CopyButton = ({ itemTask }: CopyButtonProps) => {
 		<Button
 			variant="ghost"
 			size="icon"
-			title="Copy Prompt"
+			title={t("history:copyPrompt")}
 			onClick={onCopy}
 			className="opacity-50 hover:opacity-100">
 			<span className={cn("codicon scale-80", { "codicon-check": isCopied, "codicon-copy": !isCopied })} />

+ 6 - 6
webview-ui/src/components/history/DeleteTaskDialog.tsx

@@ -13,6 +13,7 @@ import {
 	AlertDialogTitle,
 	Button,
 } from "@/components/ui"
+import { useAppTranslation } from "@/i18n/TranslationContext"
 
 import { vscode } from "@/utils/vscode"
 
@@ -21,6 +22,7 @@ interface DeleteTaskDialogProps extends AlertDialogProps {
 }
 
 export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) => {
+	const { t } = useAppTranslation()
 	const [isEnterPressed] = useKeyPress("Enter")
 
 	const { onOpenChange } = props
@@ -42,18 +44,16 @@ export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) =>
 		<AlertDialog {...props}>
 			<AlertDialogContent onEscapeKeyDown={() => onOpenChange?.(false)}>
 				<AlertDialogHeader>
-					<AlertDialogTitle>Delete Task</AlertDialogTitle>
-					<AlertDialogDescription>
-						Are you sure you want to delete this task? This action cannot be undone.
-					</AlertDialogDescription>
+					<AlertDialogTitle>{t("history:deleteTask")}</AlertDialogTitle>
+					<AlertDialogDescription>{t("history:deleteTaskMessage")}</AlertDialogDescription>
 				</AlertDialogHeader>
 				<AlertDialogFooter>
 					<AlertDialogCancel asChild>
-						<Button variant="secondary">Cancel</Button>
+						<Button variant="secondary">{t("history:cancel")}</Button>
 					</AlertDialogCancel>
 					<AlertDialogAction asChild>
 						<Button variant="destructive" onClick={onDelete}>
-							Delete
+							{t("history:delete")}
 						</Button>
 					</AlertDialogAction>
 				</AlertDialogFooter>

+ 18 - 13
webview-ui/src/components/history/ExportButton.tsx

@@ -1,16 +1,21 @@
 import { vscode } from "@/utils/vscode"
 import { Button } from "@/components/ui"
+import { useAppTranslation } from "@/i18n/TranslationContext"
 
-export const ExportButton = ({ itemId }: { itemId: string }) => (
-	<Button
-		data-testid="export"
-		variant="ghost"
-		size="icon"
-		title="Export Task"
-		onClick={(e) => {
-			e.stopPropagation()
-			vscode.postMessage({ type: "exportTaskWithId", text: itemId })
-		}}>
-		<span className="codicon codicon-cloud-download" />
-	</Button>
-)
+export const ExportButton = ({ itemId }: { itemId: string }) => {
+	const { t } = useAppTranslation()
+
+	return (
+		<Button
+			data-testid="export"
+			variant="ghost"
+			size="icon"
+			title={t("history:exportTask")}
+			onClick={(e) => {
+				e.stopPropagation()
+				vscode.postMessage({ type: "exportTaskWithId", text: itemId })
+			}}>
+			<span className="codicon codicon-cloud-download" />
+		</Button>
+	)
+}

+ 13 - 8
webview-ui/src/components/history/HistoryPreview.tsx

@@ -5,24 +5,25 @@ import { formatLargeNumber, formatDate } from "@/utils/format"
 import { Button } from "@/components/ui"
 
 import { useExtensionState } from "../../context/ExtensionStateContext"
+import { useAppTranslation } from "../../i18n/TranslationContext"
 import { CopyButton } from "./CopyButton"
 
 type HistoryPreviewProps = {
 	showHistoryView: () => void
 }
-
 const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
 	const { taskHistory } = useExtensionState()
+	const { t } = useAppTranslation()
 
 	return (
 		<div className="flex flex-col gap-3 shrink-0 mx-5">
 			<div className="flex items-center justify-between text-vscode-descriptionForeground">
 				<div className="flex items-center gap-1">
 					<span className="codicon codicon-comment-discussion scale-90 mr-1" />
-					<span className="font-medium text-xs uppercase">Recent Tasks</span>
+					<span className="font-medium text-xs uppercase">{t("history:recentTasks")}</span>
 				</div>
 				<Button variant="ghost" size="sm" onClick={() => showHistoryView()} className="uppercase">
-					View All
+					{t("history:viewAll")}
 				</Button>
 			</div>
 			{taskHistory.slice(0, 3).map((item) => (
@@ -50,22 +51,26 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
 						</div>
 						<div className="text-xs text-vscode-descriptionForeground">
 							<span>
-								Tokens: ↑{formatLargeNumber(item.tokensIn || 0)} ↓
-								{formatLargeNumber(item.tokensOut || 0)}
+								{t("history:tokens", {
+									in: formatLargeNumber(item.tokensIn || 0),
+									out: formatLargeNumber(item.tokensOut || 0),
+								})}
 							</span>
 							{!!item.cacheWrites && (
 								<>
 									{" • "}
 									<span>
-										Cache: +{formatLargeNumber(item.cacheWrites || 0)} →{" "}
-										{formatLargeNumber(item.cacheReads || 0)}
+										{t("history:cache", {
+											writes: formatLargeNumber(item.cacheWrites || 0),
+											reads: formatLargeNumber(item.cacheReads || 0),
+										})}
 									</span>
 								</>
 							)}
 							{!!item.totalCost && (
 								<>
 									{" • "}
-									<span>API Cost: ${item.totalCost?.toFixed(4)}</span>
+									<span>{t("history:apiCost", { cost: item.totalCost?.toFixed(4) })}</span>
 								</>
 							)}
 						</div>

+ 14 - 12
webview-ui/src/components/history/HistoryView.tsx

@@ -8,6 +8,7 @@ import { vscode } from "@/utils/vscode"
 import { formatLargeNumber, formatDate } from "@/utils/format"
 import { cn } from "@/lib/utils"
 import { Button } from "@/components/ui"
+import { useAppTranslation } from "@/i18n/TranslationContext"
 
 import { Tab, TabContent, TabHeader } from "../common/Tab"
 import { useTaskSearch } from "./useTaskSearch"
@@ -22,6 +23,7 @@ type SortOption = "newest" | "oldest" | "mostExpensive" | "mostTokens" | "mostRe
 
 const HistoryView = ({ onDone }: HistoryViewProps) => {
 	const { tasks, searchQuery, setSearchQuery, sortOption, setSortOption, setLastNonRelevantSort } = useTaskSearch()
+	const { t } = useAppTranslation()
 
 	const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
 
@@ -29,13 +31,13 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 		<Tab>
 			<TabHeader className="flex flex-col gap-2">
 				<div className="flex justify-between items-center">
-					<h3 className="text-vscode-foreground m-0">History</h3>
-					<VSCodeButton onClick={onDone}>Done</VSCodeButton>
+					<h3 className="text-vscode-foreground m-0">{t("history:history")}</h3>
+					<VSCodeButton onClick={onDone}>{t("history:done")}</VSCodeButton>
 				</div>
 				<div className="flex flex-col gap-2">
 					<VSCodeTextField
 						style={{ width: "100%" }}
-						placeholder="Fuzzy search history..."
+						placeholder={t("history:searchPlaceholder")}
 						value={searchQuery}
 						onInput={(e) => {
 							const newValue = (e.target as HTMLInputElement)?.value
@@ -70,15 +72,15 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 						value={sortOption}
 						role="radiogroup"
 						onChange={(e) => setSortOption((e.target as HTMLInputElement).value as SortOption)}>
-						<VSCodeRadio value="newest">Newest</VSCodeRadio>
-						<VSCodeRadio value="oldest">Oldest</VSCodeRadio>
-						<VSCodeRadio value="mostExpensive">Most Expensive</VSCodeRadio>
-						<VSCodeRadio value="mostTokens">Most Tokens</VSCodeRadio>
+						<VSCodeRadio value="newest">{t("history:newest")}</VSCodeRadio>
+						<VSCodeRadio value="oldest">{t("history:oldest")}</VSCodeRadio>
+						<VSCodeRadio value="mostExpensive">{t("history:mostExpensive")}</VSCodeRadio>
+						<VSCodeRadio value="mostTokens">{t("history:mostTokens")}</VSCodeRadio>
 						<VSCodeRadio
 							value="mostRelevant"
 							disabled={!searchQuery}
 							style={{ opacity: searchQuery ? 1 : 0.5 }}>
-							Most Relevant
+							{t("history:mostRelevant")}
 						</VSCodeRadio>
 					</VSCodeRadioGroup>
 				</div>
@@ -132,7 +134,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 										<Button
 											variant="ghost"
 											size="sm"
-											title="Delete Task (Shift + Click to skip confirmation)"
+											title={t("history:deleteTaskTitle")}
 											onClick={(e) => {
 												e.stopPropagation()
 
@@ -181,7 +183,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 													fontWeight: 500,
 													color: "var(--vscode-descriptionForeground)",
 												}}>
-												Tokens:
+												{t("history:tokensLabel")}
 											</span>
 											<span
 												data-testid="tokens-in"
@@ -242,7 +244,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 													fontWeight: 500,
 													color: "var(--vscode-descriptionForeground)",
 												}}>
-												Cache:
+												{t("history:cacheLabel")}
 											</span>
 											<span
 												data-testid="cache-writes"
@@ -297,7 +299,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 														fontWeight: 500,
 														color: "var(--vscode-descriptionForeground)",
 													}}>
-													API Cost:
+													{t("history:apiCostLabel")}
 												</span>
 												<span style={{ color: "var(--vscode-descriptionForeground)" }}>
 													${item.totalCost?.toFixed(4)}

+ 1 - 0
webview-ui/src/components/history/__tests__/HistoryView.test.tsx

@@ -7,6 +7,7 @@ import { vscode } from "../../../utils/vscode"
 
 jest.mock("../../../context/ExtensionStateContext")
 jest.mock("../../../utils/vscode")
+jest.mock("../../../i18n/TranslationContext")
 
 jest.mock("react-virtuoso", () => ({
 	Virtuoso: ({ data, itemContent }: any) => (

+ 63 - 0
webview-ui/src/i18n/__mocks__/TranslationContext.tsx

@@ -0,0 +1,63 @@
+import React from "react"
+
+// Create a mock for the useAppTranslation hook
+export const useAppTranslation = () => {
+	return {
+		t: (key: string, options?: Record<string, any>) => {
+			const translations: Record<string, string> = {
+				// History translations
+				"history:recentTasks": "Recent Tasks",
+				"history:viewAll": "View All",
+				"history:history": "History",
+				"history:done": "Done",
+				"history:searchPlaceholder": "Fuzzy search history...",
+				"history:newest": "Newest",
+				"history:oldest": "Oldest",
+				"history:mostExpensive": "Most Expensive",
+				"history:mostTokens": "Most Tokens",
+				"history:mostRelevant": "Most Relevant",
+				"history:deleteTaskTitle": "Delete Task (Shift + Click to skip confirmation)",
+				"history:tokensLabel": "Tokens:",
+				"history:cacheLabel": "Cache:",
+				"history:apiCostLabel": "API Cost:",
+				"history:copyPrompt": "Copy Prompt",
+				"history:exportTask": "Export Task",
+				"history:deleteTask": "Delete Task",
+				"history:deleteTaskMessage": "Are you sure you want to delete this task? This action cannot be undone.",
+				"history:cancel": "Cancel",
+				"history:delete": "Delete",
+			}
+
+			// Handle interpolation
+			if (options && key === "history:tokens") {
+				return `Tokens: ↑${options.in} ↓${options.out}`
+			}
+
+			if (options && key === "history:cache") {
+				return `Cache: +${options.writes} → ${options.reads}`
+			}
+
+			if (options && key === "history:apiCost") {
+				return `API Cost: $${options.cost}`
+			}
+
+			return translations[key] || key
+		},
+		i18n: {
+			language: "en",
+			changeLanguage: jest.fn(),
+		},
+	}
+}
+
+export const withTranslation = (Component: React.ComponentType<any>) => {
+	return (props: any) => <Component {...props} />
+}
+
+// Mock provider component
+export const AppTranslationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+	return <>{children}</>
+}
+
+const TranslationContext = { AppTranslationProvider, useAppTranslation, withTranslation }
+export default TranslationContext

+ 25 - 0
webview-ui/src/i18n/locales/ar/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "المهام الأخيرة",
+	"viewAll": "عرض الكل",
+	"tokens": "الرموز: ↑{{in}} ↓{{out}}",
+	"cache": "التخزين المؤقت: +{{writes}} → {{reads}}",
+	"apiCost": "تكلفة API: ${{cost}}",
+	"history": "السجل",
+	"done": "تم",
+	"searchPlaceholder": "البحث في السجل...",
+	"newest": "الأحدث",
+	"oldest": "الأقدم",
+	"mostExpensive": "الأكثر تكلفة",
+	"mostTokens": "الأكثر رموزًا",
+	"mostRelevant": "الأكثر صلة",
+	"deleteTaskTitle": "حذف المهمة (Shift + نقرة لتخطي التأكيد)",
+	"tokensLabel": "الرموز:",
+	"cacheLabel": "التخزين المؤقت:",
+	"apiCostLabel": "تكلفة API:",
+	"copyPrompt": "نسخ السؤال",
+	"exportTask": "تصدير المهمة",
+	"deleteTask": "حذف المهمة",
+	"deleteTaskMessage": "هل أنت متأكد من حذف هذه المهمة؟ لا يمكن التراجع عن هذا الإجراء.",
+	"cancel": "إلغاء",
+	"delete": "حذف"
+}

+ 25 - 0
webview-ui/src/i18n/locales/ca/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Tasques recents",
+	"viewAll": "Veure tot",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cau: +{{writes}} → {{reads}}",
+	"apiCost": "Cost d'API: ${{cost}}",
+	"history": "Historial",
+	"done": "Fet",
+	"searchPlaceholder": "Cerca a l'historial...",
+	"newest": "Més recents",
+	"oldest": "Més antigues",
+	"mostExpensive": "Més cares",
+	"mostTokens": "Més tokens",
+	"mostRelevant": "Més rellevants",
+	"deleteTaskTitle": "Eliminar tasca (Maj + Clic per ometre confirmació)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cau:",
+	"apiCostLabel": "Cost d'API:",
+	"copyPrompt": "Copiar prompt",
+	"exportTask": "Exportar tasca",
+	"deleteTask": "Eliminar tasca",
+	"deleteTaskMessage": "Estàs segur que vols eliminar aquesta tasca? Aquesta acció no es pot desfer.",
+	"cancel": "Cancel·lar",
+	"delete": "Eliminar"
+}

+ 25 - 0
webview-ui/src/i18n/locales/cs/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Nedávné úkoly",
+	"viewAll": "Zobrazit vše",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Mezipaměť: +{{writes}} → {{reads}}",
+	"apiCost": "Náklady API: ${{cost}}",
+	"history": "Historie",
+	"done": "Hotovo",
+	"searchPlaceholder": "Vyhledat v historii...",
+	"newest": "Nejnovější",
+	"oldest": "Nejstarší",
+	"mostExpensive": "Nejdražší",
+	"mostTokens": "Nejvíce tokenů",
+	"mostRelevant": "Nejrelevantnější",
+	"deleteTaskTitle": "Smazat úkol (Shift + klik pro přeskočení potvrzení)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Mezipaměť:",
+	"apiCostLabel": "Náklady API:",
+	"copyPrompt": "Kopírovat prompt",
+	"exportTask": "Exportovat úkol",
+	"deleteTask": "Smazat úkol",
+	"deleteTaskMessage": "Opravdu chcete smazat tento úkol? Tuto akci nelze vrátit zpět.",
+	"cancel": "Zrušit",
+	"delete": "Smazat"
+}

+ 25 - 0
webview-ui/src/i18n/locales/de/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Neueste Aufgaben",
+	"viewAll": "Alle anzeigen",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cache: +{{writes}} → {{reads}}",
+	"apiCost": "API-Kosten: ${{cost}}",
+	"history": "Verlauf",
+	"done": "Fertig",
+	"searchPlaceholder": "Verlauf durchsuchen...",
+	"newest": "Neueste",
+	"oldest": "Älteste",
+	"mostExpensive": "Teuerste",
+	"mostTokens": "Meiste Tokens",
+	"mostRelevant": "Relevanteste",
+	"deleteTaskTitle": "Aufgabe löschen (Umschalt + Klick um Bestätigung zu überspringen)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cache:",
+	"apiCostLabel": "API-Kosten:",
+	"copyPrompt": "Prompt kopieren",
+	"exportTask": "Aufgabe exportieren",
+	"deleteTask": "Aufgabe löschen",
+	"deleteTaskMessage": "Sind Sie sicher, dass Sie diese Aufgabe löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
+	"cancel": "Abbrechen",
+	"delete": "Löschen"
+}

+ 25 - 0
webview-ui/src/i18n/locales/en/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Recent Tasks",
+	"viewAll": "View All",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cache: +{{writes}} → {{reads}}",
+	"apiCost": "API Cost: ${{cost}}",
+	"history": "History",
+	"done": "Done",
+	"searchPlaceholder": "Fuzzy search history...",
+	"newest": "Newest",
+	"oldest": "Oldest",
+	"mostExpensive": "Most Expensive",
+	"mostTokens": "Most Tokens",
+	"mostRelevant": "Most Relevant",
+	"deleteTaskTitle": "Delete Task (Shift + Click to skip confirmation)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cache:",
+	"apiCostLabel": "API Cost:",
+	"copyPrompt": "Copy Prompt",
+	"exportTask": "Export Task",
+	"deleteTask": "Delete Task",
+	"deleteTaskMessage": "Are you sure you want to delete this task? This action cannot be undone.",
+	"cancel": "Cancel",
+	"delete": "Delete"
+}

+ 25 - 0
webview-ui/src/i18n/locales/es/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Tareas recientes",
+	"viewAll": "Ver todas",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Caché: +{{writes}} → {{reads}}",
+	"apiCost": "Costo de API: ${{cost}}",
+	"history": "Historial",
+	"done": "Listo",
+	"searchPlaceholder": "Buscar en el historial...",
+	"newest": "Más recientes",
+	"oldest": "Más antiguas",
+	"mostExpensive": "Más costosas",
+	"mostTokens": "Más tokens",
+	"mostRelevant": "Más relevantes",
+	"deleteTaskTitle": "Eliminar tarea (Shift + Clic para omitir confirmación)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Caché:",
+	"apiCostLabel": "Costo de API:",
+	"copyPrompt": "Copiar prompt",
+	"exportTask": "Exportar tarea",
+	"deleteTask": "Eliminar tarea",
+	"deleteTaskMessage": "¿Estás seguro de que quieres eliminar esta tarea? Esta acción no se puede deshacer.",
+	"cancel": "Cancelar",
+	"delete": "Eliminar"
+}

+ 25 - 0
webview-ui/src/i18n/locales/fr/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Tâches récentes",
+	"viewAll": "Voir tout",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cache: +{{writes}} → {{reads}}",
+	"apiCost": "Coût API: ${{cost}}",
+	"history": "Historique",
+	"done": "Terminé",
+	"searchPlaceholder": "Rechercher dans l'historique...",
+	"newest": "Plus récentes",
+	"oldest": "Plus anciennes",
+	"mostExpensive": "Plus coûteuses",
+	"mostTokens": "Plus de tokens",
+	"mostRelevant": "Plus pertinentes",
+	"deleteTaskTitle": "Supprimer la tâche (Maj + Clic pour ignorer la confirmation)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cache:",
+	"apiCostLabel": "Coût API:",
+	"copyPrompt": "Copier le prompt",
+	"exportTask": "Exporter la tâche",
+	"deleteTask": "Supprimer la tâche",
+	"deleteTaskMessage": "Êtes-vous sûr de vouloir supprimer cette tâche ? Cette action ne peut pas être annulée.",
+	"cancel": "Annuler",
+	"delete": "Supprimer"
+}

+ 25 - 0
webview-ui/src/i18n/locales/hi/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "हालिया कार्य",
+	"viewAll": "सभी देखें",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "कैश: +{{writes}} → {{reads}}",
+	"apiCost": "API लागत: ${{cost}}",
+	"history": "इतिहास",
+	"done": "पूर्ण",
+	"searchPlaceholder": "इतिहास खोजें...",
+	"newest": "नवीनतम",
+	"oldest": "सबसे पुराना",
+	"mostExpensive": "सबसे महंगा",
+	"mostTokens": "सबसे अधिक टोकन",
+	"mostRelevant": "सबसे प्रासंगिक",
+	"deleteTaskTitle": "कार्य हटाएं (Shift + क्लिक पुष्टि छोड़ने के लिए)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "कैश:",
+	"apiCostLabel": "API लागत:",
+	"copyPrompt": "प्रॉम्प्ट कॉपी करें",
+	"exportTask": "कार्य निर्यात करें",
+	"deleteTask": "कार्य हटाएं",
+	"deleteTaskMessage": "क्या आप वाकई इस कार्य को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती है।",
+	"cancel": "रद्द करें",
+	"delete": "हटाएं"
+}

+ 25 - 0
webview-ui/src/i18n/locales/hu/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Legutóbbi feladatok",
+	"viewAll": "Összes megtekintése",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Gyorsítótár: +{{writes}} → {{reads}}",
+	"apiCost": "API költség: ${{cost}}",
+	"history": "Előzmények",
+	"done": "Kész",
+	"searchPlaceholder": "Előzmények keresése...",
+	"newest": "Legújabb",
+	"oldest": "Legrégebbi",
+	"mostExpensive": "Legdrágább",
+	"mostTokens": "Legtöbb token",
+	"mostRelevant": "Legrelevánsabb",
+	"deleteTaskTitle": "Feladat törlése (Shift + kattintás a megerősítés kihagyásához)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Gyorsítótár:",
+	"apiCostLabel": "API költség:",
+	"copyPrompt": "Prompt másolása",
+	"exportTask": "Feladat exportálása",
+	"deleteTask": "Feladat törlése",
+	"deleteTaskMessage": "Biztosan törölni szeretné ezt a feladatot? Ez a művelet nem vonható vissza.",
+	"cancel": "Mégsem",
+	"delete": "Törlés"
+}

+ 25 - 0
webview-ui/src/i18n/locales/it/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Attività recenti",
+	"viewAll": "Vedi tutto",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cache: +{{writes}} → {{reads}}",
+	"apiCost": "Costo API: ${{cost}}",
+	"history": "Cronologia",
+	"done": "Fatto",
+	"searchPlaceholder": "Ricerca nella cronologia...",
+	"newest": "Più recenti",
+	"oldest": "Più vecchie",
+	"mostExpensive": "Più costose",
+	"mostTokens": "Più token",
+	"mostRelevant": "Più rilevanti",
+	"deleteTaskTitle": "Elimina attività (Shift + Clic per saltare conferma)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cache:",
+	"apiCostLabel": "Costo API:",
+	"copyPrompt": "Copia prompt",
+	"exportTask": "Esporta attività",
+	"deleteTask": "Elimina attività",
+	"deleteTaskMessage": "Sei sicuro di voler eliminare questa attività? Questa azione non può essere annullata.",
+	"cancel": "Annulla",
+	"delete": "Elimina"
+}

+ 25 - 0
webview-ui/src/i18n/locales/ja/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "最近のタスク",
+	"viewAll": "すべて表示",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "キャッシュ: +{{writes}} → {{reads}}",
+	"apiCost": "API コスト: ${{cost}}",
+	"history": "履歴",
+	"done": "完了",
+	"searchPlaceholder": "履歴をあいまい検索...",
+	"newest": "最新",
+	"oldest": "最古",
+	"mostExpensive": "最も高価",
+	"mostTokens": "最多トークン",
+	"mostRelevant": "最も関連性の高い",
+	"deleteTaskTitle": "タスクを削除(Shift + クリックで確認をスキップ)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "キャッシュ:",
+	"apiCostLabel": "API コスト:",
+	"copyPrompt": "プロンプトをコピー",
+	"exportTask": "タスクをエクスポート",
+	"deleteTask": "タスクを削除",
+	"deleteTaskMessage": "このタスクを削除してもよろしいですか?この操作は元に戻せません。",
+	"cancel": "キャンセル",
+	"delete": "削除"
+}

+ 25 - 0
webview-ui/src/i18n/locales/ko/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "최근 작업",
+	"viewAll": "모두 보기",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "캐시: +{{writes}} → {{reads}}",
+	"apiCost": "API 비용: ${{cost}}",
+	"history": "기록",
+	"done": "완료",
+	"searchPlaceholder": "기록 검색...",
+	"newest": "최신순",
+	"oldest": "오래된순",
+	"mostExpensive": "가장 비싼순",
+	"mostTokens": "토큰 많은순",
+	"mostRelevant": "관련성 높은순",
+	"deleteTaskTitle": "작업 삭제 (Shift + 클릭으로 확인 생략)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "캐시:",
+	"apiCostLabel": "API 비용:",
+	"copyPrompt": "프롬프트 복사",
+	"exportTask": "작업 내보내기",
+	"deleteTask": "작업 삭제",
+	"deleteTaskMessage": "이 작업을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
+	"cancel": "취소",
+	"delete": "삭제"
+}

+ 25 - 0
webview-ui/src/i18n/locales/pl/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Ostatnie zadania",
+	"viewAll": "Zobacz wszystkie",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Pamięć podręczna: +{{writes}} → {{reads}}",
+	"apiCost": "Koszt API: ${{cost}}",
+	"history": "Historia",
+	"done": "Gotowe",
+	"searchPlaceholder": "Szukaj w historii...",
+	"newest": "Najnowsze",
+	"oldest": "Najstarsze",
+	"mostExpensive": "Najdroższe",
+	"mostTokens": "Najwięcej tokenów",
+	"mostRelevant": "Najbardziej trafne",
+	"deleteTaskTitle": "Usuń zadanie (Shift + Klik, aby pominąć potwierdzenie)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Pamięć podręczna:",
+	"apiCostLabel": "Koszt API:",
+	"copyPrompt": "Kopiuj prompt",
+	"exportTask": "Eksportuj zadanie",
+	"deleteTask": "Usuń zadanie",
+	"deleteTaskMessage": "Czy na pewno chcesz usunąć to zadanie? Tej akcji nie można cofnąć.",
+	"cancel": "Anuluj",
+	"delete": "Usuń"
+}

+ 25 - 0
webview-ui/src/i18n/locales/pt-BR/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Tarefas recentes",
+	"viewAll": "Ver todas",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cache: +{{writes}} → {{reads}}",
+	"apiCost": "Custo da API: ${{cost}}",
+	"history": "Histórico",
+	"done": "Concluído",
+	"searchPlaceholder": "Pesquisar no histórico...",
+	"newest": "Mais recentes",
+	"oldest": "Mais antigas",
+	"mostExpensive": "Mais caras",
+	"mostTokens": "Mais tokens",
+	"mostRelevant": "Mais relevantes",
+	"deleteTaskTitle": "Excluir tarefa (Shift + Clique para pular confirmação)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cache:",
+	"apiCostLabel": "Custo da API:",
+	"copyPrompt": "Copiar prompt",
+	"exportTask": "Exportar tarefa",
+	"deleteTask": "Excluir tarefa",
+	"deleteTaskMessage": "Tem certeza que deseja excluir esta tarefa? Esta ação não pode ser desfeita.",
+	"cancel": "Cancelar",
+	"delete": "Excluir"
+}

+ 25 - 0
webview-ui/src/i18n/locales/pt/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Tarefas recentes",
+	"viewAll": "Ver todas",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Cache: +{{writes}} → {{reads}}",
+	"apiCost": "Custo da API: ${{cost}}",
+	"history": "Histórico",
+	"done": "Concluído",
+	"searchPlaceholder": "Pesquisar no histórico...",
+	"newest": "Mais recentes",
+	"oldest": "Mais antigas",
+	"mostExpensive": "Mais caras",
+	"mostTokens": "Mais tokens",
+	"mostRelevant": "Mais relevantes",
+	"deleteTaskTitle": "Eliminar tarefa (Shift + Clique para ignorar confirmação)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Cache:",
+	"apiCostLabel": "Custo da API:",
+	"copyPrompt": "Copiar prompt",
+	"exportTask": "Exportar tarefa",
+	"deleteTask": "Eliminar tarefa",
+	"deleteTaskMessage": "Tem a certeza que pretende eliminar esta tarefa? Esta ação não pode ser desfeita.",
+	"cancel": "Cancelar",
+	"delete": "Eliminar"
+}

+ 25 - 0
webview-ui/src/i18n/locales/ru/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Недавние задачи",
+	"viewAll": "Показать все",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Кэш: +{{writes}} → {{reads}}",
+	"apiCost": "Стоимость API: ${{cost}}",
+	"history": "История",
+	"done": "Готово",
+	"searchPlaceholder": "Поиск по истории...",
+	"newest": "Новейшие",
+	"oldest": "Старейшие",
+	"mostExpensive": "Самые дорогие",
+	"mostTokens": "Больше всего токенов",
+	"mostRelevant": "Наиболее релевантные",
+	"deleteTaskTitle": "Удалить задачу (Shift + Клик для пропуска подтверждения)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Кэш:",
+	"apiCostLabel": "Стоимость API:",
+	"copyPrompt": "Копировать запрос",
+	"exportTask": "Экспортировать задачу",
+	"deleteTask": "Удалить задачу",
+	"deleteTaskMessage": "Вы уверены, что хотите удалить эту задачу? Это действие нельзя отменить.",
+	"cancel": "Отмена",
+	"delete": "Удалить"
+}

+ 25 - 0
webview-ui/src/i18n/locales/tr/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "Son Görevler",
+	"viewAll": "Tümünü Gör",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "Önbellek: +{{writes}} → {{reads}}",
+	"apiCost": "API Maliyeti: ${{cost}}",
+	"history": "Geçmiş",
+	"done": "Tamam",
+	"searchPlaceholder": "Geçmişte ara...",
+	"newest": "En Yeni",
+	"oldest": "En Eski",
+	"mostExpensive": "En Pahalı",
+	"mostTokens": "En Çok Token",
+	"mostRelevant": "En İlgili",
+	"deleteTaskTitle": "Görevi Sil (Onayı atlamak için Shift + Tıkla)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "Önbellek:",
+	"apiCostLabel": "API Maliyeti:",
+	"copyPrompt": "Promptu Kopyala",
+	"exportTask": "Görevi Dışa Aktar",
+	"deleteTask": "Görevi Sil",
+	"deleteTaskMessage": "Bu görevi silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
+	"cancel": "İptal",
+	"delete": "Sil"
+}

+ 25 - 0
webview-ui/src/i18n/locales/zh-CN/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "最近任务",
+	"viewAll": "查看全部",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "缓存: +{{writes}} → {{reads}}",
+	"apiCost": "API 成本: ${{cost}}",
+	"history": "历史记录",
+	"done": "完成",
+	"searchPlaceholder": "模糊搜索历史...",
+	"newest": "最新",
+	"oldest": "最早",
+	"mostExpensive": "最贵",
+	"mostTokens": "最多令牌",
+	"mostRelevant": "最相关",
+	"deleteTaskTitle": "删除任务(Shift + 点击跳过确认)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "缓存:",
+	"apiCostLabel": "API 成本:",
+	"copyPrompt": "复制提示词",
+	"exportTask": "导出任务",
+	"deleteTask": "删除任务",
+	"deleteTaskMessage": "确定要删除此任务吗?此操作无法撤销。",
+	"cancel": "取消",
+	"delete": "删除"
+}

+ 25 - 0
webview-ui/src/i18n/locales/zh-TW/history.json

@@ -0,0 +1,25 @@
+{
+	"recentTasks": "最近任務",
+	"viewAll": "查看全部",
+	"tokens": "Tokens: ↑{{in}} ↓{{out}}",
+	"cache": "快取: +{{writes}} → {{reads}}",
+	"apiCost": "API 費用: ${{cost}}",
+	"history": "歷史記錄",
+	"done": "完成",
+	"searchPlaceholder": "模糊搜尋歷史...",
+	"newest": "最新",
+	"oldest": "最舊",
+	"mostExpensive": "最昂貴",
+	"mostTokens": "最多令牌",
+	"mostRelevant": "最相關",
+	"deleteTaskTitle": "刪除任務(Shift + 點擊跳過確認)",
+	"tokensLabel": "Tokens:",
+	"cacheLabel": "快取:",
+	"apiCostLabel": "API 費用:",
+	"copyPrompt": "複製提示詞",
+	"exportTask": "匯出任務",
+	"deleteTask": "刪除任務",
+	"deleteTaskMessage": "確定要刪除此任務嗎?此操作無法復原。",
+	"cancel": "取消",
+	"delete": "刪除"
+}