Skip to content

Commit 5af07d2

Browse files
authored
Merge pull request #1689 from RooVetGit/i18n_history
Translate history preview and view
2 parents 8f4d56c + 6cdd102 commit 5af07d2

28 files changed

+598
-47
lines changed

.roomodes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"slug": "translate",
2323
"name": "Translate",
2424
"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.",
25-
"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",
25+
"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",
2626
"groups": [
2727
"read",
2828
[

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -940,12 +940,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
940940
[],
941941
)
942942

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

950948
const itemContent = useCallback(
951949
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {

webview-ui/src/components/history/CopyButton.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { useCallback } from "react"
33
import { useClipboard } from "@/components/ui/hooks"
44
import { Button } from "@/components/ui"
55
import { cn } from "@/lib/utils"
6+
import { useAppTranslation } from "@/i18n/TranslationContext"
67

78
type CopyButtonProps = {
89
itemTask: string
910
}
1011

1112
export const CopyButton = ({ itemTask }: CopyButtonProps) => {
1213
const { isCopied, copy } = useClipboard()
14+
const { t } = useAppTranslation()
1315

1416
const onCopy = useCallback(
1517
(e: React.MouseEvent) => {
@@ -23,7 +25,7 @@ export const CopyButton = ({ itemTask }: CopyButtonProps) => {
2325
<Button
2426
variant="ghost"
2527
size="icon"
26-
title="Copy Prompt"
28+
title={t("history:copyPrompt")}
2729
onClick={onCopy}
2830
className="opacity-50 hover:opacity-100">
2931
<span className={cn("codicon scale-80", { "codicon-check": isCopied, "codicon-copy": !isCopied })} />

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
AlertDialogTitle,
1414
Button,
1515
} from "@/components/ui"
16+
import { useAppTranslation } from "@/i18n/TranslationContext"
1617

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

@@ -21,6 +22,7 @@ interface DeleteTaskDialogProps extends AlertDialogProps {
2122
}
2223

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

2628
const { onOpenChange } = props
@@ -42,18 +44,16 @@ export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) =>
4244
<AlertDialog {...props}>
4345
<AlertDialogContent onEscapeKeyDown={() => onOpenChange?.(false)}>
4446
<AlertDialogHeader>
45-
<AlertDialogTitle>Delete Task</AlertDialogTitle>
46-
<AlertDialogDescription>
47-
Are you sure you want to delete this task? This action cannot be undone.
48-
</AlertDialogDescription>
47+
<AlertDialogTitle>{t("history:deleteTask")}</AlertDialogTitle>
48+
<AlertDialogDescription>{t("history:deleteTaskMessage")}</AlertDialogDescription>
4949
</AlertDialogHeader>
5050
<AlertDialogFooter>
5151
<AlertDialogCancel asChild>
52-
<Button variant="secondary">Cancel</Button>
52+
<Button variant="secondary">{t("history:cancel")}</Button>
5353
</AlertDialogCancel>
5454
<AlertDialogAction asChild>
5555
<Button variant="destructive" onClick={onDelete}>
56-
Delete
56+
{t("history:delete")}
5757
</Button>
5858
</AlertDialogAction>
5959
</AlertDialogFooter>
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import { vscode } from "@/utils/vscode"
22
import { Button } from "@/components/ui"
3+
import { useAppTranslation } from "@/i18n/TranslationContext"
34

4-
export const ExportButton = ({ itemId }: { itemId: string }) => (
5-
<Button
6-
data-testid="export"
7-
variant="ghost"
8-
size="icon"
9-
title="Export Task"
10-
onClick={(e) => {
11-
e.stopPropagation()
12-
vscode.postMessage({ type: "exportTaskWithId", text: itemId })
13-
}}>
14-
<span className="codicon codicon-cloud-download" />
15-
</Button>
16-
)
5+
export const ExportButton = ({ itemId }: { itemId: string }) => {
6+
const { t } = useAppTranslation()
7+
8+
return (
9+
<Button
10+
data-testid="export"
11+
variant="ghost"
12+
size="icon"
13+
title={t("history:exportTask")}
14+
onClick={(e) => {
15+
e.stopPropagation()
16+
vscode.postMessage({ type: "exportTaskWithId", text: itemId })
17+
}}>
18+
<span className="codicon codicon-cloud-download" />
19+
</Button>
20+
)
21+
}

webview-ui/src/components/history/HistoryPreview.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,25 @@ import { formatLargeNumber, formatDate } from "@/utils/format"
55
import { Button } from "@/components/ui"
66

77
import { useExtensionState } from "../../context/ExtensionStateContext"
8+
import { useAppTranslation } from "../../i18n/TranslationContext"
89
import { CopyButton } from "./CopyButton"
910

1011
type HistoryPreviewProps = {
1112
showHistoryView: () => void
1213
}
13-
1414
const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
1515
const { taskHistory } = useExtensionState()
16+
const { t } = useAppTranslation()
1617

1718
return (
1819
<div className="flex flex-col gap-3 shrink-0 mx-5">
1920
<div className="flex items-center justify-between text-vscode-descriptionForeground">
2021
<div className="flex items-center gap-1">
2122
<span className="codicon codicon-comment-discussion scale-90 mr-1" />
22-
<span className="font-medium text-xs uppercase">Recent Tasks</span>
23+
<span className="font-medium text-xs uppercase">{t("history:recentTasks")}</span>
2324
</div>
2425
<Button variant="ghost" size="sm" onClick={() => showHistoryView()} className="uppercase">
25-
View All
26+
{t("history:viewAll")}
2627
</Button>
2728
</div>
2829
{taskHistory.slice(0, 3).map((item) => (
@@ -50,22 +51,26 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
5051
</div>
5152
<div className="text-xs text-vscode-descriptionForeground">
5253
<span>
53-
Tokens: ↑{formatLargeNumber(item.tokensIn || 0)}
54-
{formatLargeNumber(item.tokensOut || 0)}
54+
{t("history:tokens", {
55+
in: formatLargeNumber(item.tokensIn || 0),
56+
out: formatLargeNumber(item.tokensOut || 0),
57+
})}
5558
</span>
5659
{!!item.cacheWrites && (
5760
<>
5861
{" • "}
5962
<span>
60-
Cache: +{formatLargeNumber(item.cacheWrites || 0)}{" "}
61-
{formatLargeNumber(item.cacheReads || 0)}
63+
{t("history:cache", {
64+
writes: formatLargeNumber(item.cacheWrites || 0),
65+
reads: formatLargeNumber(item.cacheReads || 0),
66+
})}
6267
</span>
6368
</>
6469
)}
6570
{!!item.totalCost && (
6671
<>
6772
{" • "}
68-
<span>API Cost: ${item.totalCost?.toFixed(4)}</span>
73+
<span>{t("history:apiCost", { cost: item.totalCost?.toFixed(4) })}</span>
6974
</>
7075
)}
7176
</div>

webview-ui/src/components/history/HistoryView.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { vscode } from "@/utils/vscode"
88
import { formatLargeNumber, formatDate } from "@/utils/format"
99
import { cn } from "@/lib/utils"
1010
import { Button } from "@/components/ui"
11+
import { useAppTranslation } from "@/i18n/TranslationContext"
1112

1213
import { Tab, TabContent, TabHeader } from "../common/Tab"
1314
import { useTaskSearch } from "./useTaskSearch"
@@ -22,20 +23,21 @@ type SortOption = "newest" | "oldest" | "mostExpensive" | "mostTokens" | "mostRe
2223

2324
const HistoryView = ({ onDone }: HistoryViewProps) => {
2425
const { tasks, searchQuery, setSearchQuery, sortOption, setSortOption, setLastNonRelevantSort } = useTaskSearch()
26+
const { t } = useAppTranslation()
2527

2628
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
2729

2830
return (
2931
<Tab>
3032
<TabHeader className="flex flex-col gap-2">
3133
<div className="flex justify-between items-center">
32-
<h3 className="text-vscode-foreground m-0">History</h3>
33-
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
34+
<h3 className="text-vscode-foreground m-0">{t("history:history")}</h3>
35+
<VSCodeButton onClick={onDone}>{t("history:done")}</VSCodeButton>
3436
</div>
3537
<div className="flex flex-col gap-2">
3638
<VSCodeTextField
3739
style={{ width: "100%" }}
38-
placeholder="Fuzzy search history..."
40+
placeholder={t("history:searchPlaceholder")}
3941
value={searchQuery}
4042
onInput={(e) => {
4143
const newValue = (e.target as HTMLInputElement)?.value
@@ -70,15 +72,15 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
7072
value={sortOption}
7173
role="radiogroup"
7274
onChange={(e) => setSortOption((e.target as HTMLInputElement).value as SortOption)}>
73-
<VSCodeRadio value="newest">Newest</VSCodeRadio>
74-
<VSCodeRadio value="oldest">Oldest</VSCodeRadio>
75-
<VSCodeRadio value="mostExpensive">Most Expensive</VSCodeRadio>
76-
<VSCodeRadio value="mostTokens">Most Tokens</VSCodeRadio>
75+
<VSCodeRadio value="newest">{t("history:newest")}</VSCodeRadio>
76+
<VSCodeRadio value="oldest">{t("history:oldest")}</VSCodeRadio>
77+
<VSCodeRadio value="mostExpensive">{t("history:mostExpensive")}</VSCodeRadio>
78+
<VSCodeRadio value="mostTokens">{t("history:mostTokens")}</VSCodeRadio>
7779
<VSCodeRadio
7880
value="mostRelevant"
7981
disabled={!searchQuery}
8082
style={{ opacity: searchQuery ? 1 : 0.5 }}>
81-
Most Relevant
83+
{t("history:mostRelevant")}
8284
</VSCodeRadio>
8385
</VSCodeRadioGroup>
8486
</div>
@@ -132,7 +134,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
132134
<Button
133135
variant="ghost"
134136
size="sm"
135-
title="Delete Task (Shift + Click to skip confirmation)"
137+
title={t("history:deleteTaskTitle")}
136138
onClick={(e) => {
137139
e.stopPropagation()
138140

@@ -181,7 +183,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
181183
fontWeight: 500,
182184
color: "var(--vscode-descriptionForeground)",
183185
}}>
184-
Tokens:
186+
{t("history:tokensLabel")}
185187
</span>
186188
<span
187189
data-testid="tokens-in"
@@ -242,7 +244,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
242244
fontWeight: 500,
243245
color: "var(--vscode-descriptionForeground)",
244246
}}>
245-
Cache:
247+
{t("history:cacheLabel")}
246248
</span>
247249
<span
248250
data-testid="cache-writes"
@@ -297,7 +299,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
297299
fontWeight: 500,
298300
color: "var(--vscode-descriptionForeground)",
299301
}}>
300-
API Cost:
302+
{t("history:apiCostLabel")}
301303
</span>
302304
<span style={{ color: "var(--vscode-descriptionForeground)" }}>
303305
${item.totalCost?.toFixed(4)}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { vscode } from "../../../utils/vscode"
77

88
jest.mock("../../../context/ExtensionStateContext")
99
jest.mock("../../../utils/vscode")
10+
jest.mock("../../../i18n/TranslationContext")
1011

1112
jest.mock("react-virtuoso", () => ({
1213
Virtuoso: ({ data, itemContent }: any) => (
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from "react"
2+
3+
// Create a mock for the useAppTranslation hook
4+
export const useAppTranslation = () => {
5+
return {
6+
t: (key: string, options?: Record<string, any>) => {
7+
const translations: Record<string, string> = {
8+
// History translations
9+
"history:recentTasks": "Recent Tasks",
10+
"history:viewAll": "View All",
11+
"history:history": "History",
12+
"history:done": "Done",
13+
"history:searchPlaceholder": "Fuzzy search history...",
14+
"history:newest": "Newest",
15+
"history:oldest": "Oldest",
16+
"history:mostExpensive": "Most Expensive",
17+
"history:mostTokens": "Most Tokens",
18+
"history:mostRelevant": "Most Relevant",
19+
"history:deleteTaskTitle": "Delete Task (Shift + Click to skip confirmation)",
20+
"history:tokensLabel": "Tokens:",
21+
"history:cacheLabel": "Cache:",
22+
"history:apiCostLabel": "API Cost:",
23+
"history:copyPrompt": "Copy Prompt",
24+
"history:exportTask": "Export Task",
25+
"history:deleteTask": "Delete Task",
26+
"history:deleteTaskMessage": "Are you sure you want to delete this task? This action cannot be undone.",
27+
"history:cancel": "Cancel",
28+
"history:delete": "Delete",
29+
}
30+
31+
// Handle interpolation
32+
if (options && key === "history:tokens") {
33+
return `Tokens: ↑${options.in}${options.out}`
34+
}
35+
36+
if (options && key === "history:cache") {
37+
return `Cache: +${options.writes}${options.reads}`
38+
}
39+
40+
if (options && key === "history:apiCost") {
41+
return `API Cost: $${options.cost}`
42+
}
43+
44+
return translations[key] || key
45+
},
46+
i18n: {
47+
language: "en",
48+
changeLanguage: jest.fn(),
49+
},
50+
}
51+
}
52+
53+
export const withTranslation = (Component: React.ComponentType<any>) => {
54+
return (props: any) => <Component {...props} />
55+
}
56+
57+
// Mock provider component
58+
export const AppTranslationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
59+
return <>{children}</>
60+
}
61+
62+
const TranslationContext = { AppTranslationProvider, useAppTranslation, withTranslation }
63+
export default TranslationContext
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"recentTasks": "المهام الأخيرة",
3+
"viewAll": "عرض الكل",
4+
"tokens": "الرموز: ↑{{in}} ↓{{out}}",
5+
"cache": "التخزين المؤقت: +{{writes}} → {{reads}}",
6+
"apiCost": "تكلفة API: ${{cost}}",
7+
"history": "السجل",
8+
"done": "تم",
9+
"searchPlaceholder": "البحث في السجل...",
10+
"newest": "الأحدث",
11+
"oldest": "الأقدم",
12+
"mostExpensive": "الأكثر تكلفة",
13+
"mostTokens": "الأكثر رموزًا",
14+
"mostRelevant": "الأكثر صلة",
15+
"deleteTaskTitle": "حذف المهمة (Shift + نقرة لتخطي التأكيد)",
16+
"tokensLabel": "الرموز:",
17+
"cacheLabel": "التخزين المؤقت:",
18+
"apiCostLabel": "تكلفة API:",
19+
"copyPrompt": "نسخ السؤال",
20+
"exportTask": "تصدير المهمة",
21+
"deleteTask": "حذف المهمة",
22+
"deleteTaskMessage": "هل أنت متأكد من حذف هذه المهمة؟ لا يمكن التراجع عن هذا الإجراء.",
23+
"cancel": "إلغاء",
24+
"delete": "حذف"
25+
}

0 commit comments

Comments
 (0)