Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .roomodes
Original file line number Diff line number Diff line change
Expand Up @@ -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",
[
Expand Down
10 changes: 4 additions & 6 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -940,12 +940,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
[],
)

const placeholderText = useMemo(() => {
const baseText = task ? t("chat:typeMessage") : t("chat:typeTask")
const contextText = t("chat:addContext")
const imageText = shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}`
return baseText + `\n(${contextText}${imageText})`
}, [task, shouldDisableImages, t])
const baseText = task ? t("chat:typeMessage") : t("chat:typeTask")
const placeholderText =
baseText +
`\n(${t("chat:addContext")}${shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}`})`

const itemContent = useCallback(
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {
Expand Down
4 changes: 3 additions & 1 deletion webview-ui/src/components/history/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ 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
}

export const CopyButton = ({ itemTask }: CopyButtonProps) => {
const { isCopied, copy } = useClipboard()
const { t } = useAppTranslation()

const onCopy = useCallback(
(e: React.MouseEvent) => {
Expand All @@ -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 })} />
Expand Down
12 changes: 6 additions & 6 deletions webview-ui/src/components/history/DeleteTaskDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
AlertDialogTitle,
Button,
} from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"

import { vscode } from "@/utils/vscode"

Expand All @@ -21,6 +22,7 @@ interface DeleteTaskDialogProps extends AlertDialogProps {
}

export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) => {
const { t } = useAppTranslation()
const [isEnterPressed] = useKeyPress("Enter")

const { onOpenChange } = props
Expand All @@ -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>
Expand Down
31 changes: 18 additions & 13 deletions webview-ui/src/components/history/ExportButton.tsx
Original file line number Diff line number Diff line change
@@ -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>
)
}
21 changes: 13 additions & 8 deletions webview-ui/src/components/history/HistoryPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (
Expand Down Expand Up @@ -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>
Expand Down
26 changes: 14 additions & 12 deletions webview-ui/src/components/history/HistoryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -22,20 +23,21 @@ 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)

return (
<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
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -181,7 +183,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
fontWeight: 500,
color: "var(--vscode-descriptionForeground)",
}}>
Tokens:
{t("history:tokensLabel")}
</span>
<span
data-testid="tokens-in"
Expand Down Expand Up @@ -242,7 +244,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
fontWeight: 500,
color: "var(--vscode-descriptionForeground)",
}}>
Cache:
{t("history:cacheLabel")}
</span>
<span
data-testid="cache-writes"
Expand Down Expand Up @@ -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)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (
Expand Down
63 changes: 63 additions & 0 deletions webview-ui/src/i18n/__mocks__/TranslationContext.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions webview-ui/src/i18n/locales/ar/history.json
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions webview-ui/src/i18n/locales/ca/history.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading