Skip to content

Commit 1d90c35

Browse files
committedDec 9, 2024·
feat: add i18n
1 parent 4dd301b commit 1d90c35

File tree

13 files changed

+132
-20
lines changed

13 files changed

+132
-20
lines changed
 

‎.nvmrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
lts/*
1+
v20.18.0

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "claude-dev",
33
"displayName": "Cline (prev. Claude Dev)",
44
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
5-
"version": "2.1.11",
5+
"version": "2.1.12",
66
"icon": "assets/icons/icon.png",
77
"galleryBanner": {
88
"color": "#617A91",

‎src/core/webview/ClineProvider.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getTheme } from "../../integrations/theme/getTheme"
1212
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
1313
import { ApiProvider, ModelInfo } from "../../shared/api"
1414
import { findLast } from "../../shared/array"
15-
import { ExtensionMessage } from "../../shared/ExtensionMessage"
15+
import { ExtensionMessage, Locale } from "../../shared/ExtensionMessage"
1616
import { HistoryItem } from "../../shared/HistoryItem"
1717
import { WebviewMessage } from "../../shared/WebviewMessage"
1818
import { fileExistsAtPath } from "../../utils/fs"
@@ -333,6 +333,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
333333
}
334334
}
335335
})
336+
this.postMessageToWebview({ type: "locale", locale: vscode.env.language as Locale })
336337
break
337338
case "newTask":
338339
// Code that should run in response to the hello message command

‎src/shared/ExtensionMessage.ts

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import { ApiConfiguration, ModelInfo } from "./api"
44
import { HistoryItem } from "./HistoryItem"
55

6+
7+
export type Locale = "en" | "zh-cn"
8+
69
// webview will hold state
710
export interface ExtensionMessage {
811
type:
@@ -16,6 +19,7 @@ export interface ExtensionMessage {
1619
| "invoke"
1720
| "partialMessage"
1821
| "openRouterModels"
22+
| "locale"
1923
text?: string
2024
action?: "chatButtonClicked" | "settingsButtonClicked" | "historyButtonClicked" | "didBecomeVisible"
2125
invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick"
@@ -26,6 +30,7 @@ export interface ExtensionMessage {
2630
filePaths?: string[]
2731
partialMessage?: ClineMessage
2832
openRouterModels?: Record<string, ModelInfo>
33+
locale?: Locale
2934
}
3035

3136
export interface ExtensionState {

‎webview-ui/package-lock.json

+40
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎webview-ui/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
"rewire": "^7.0.0",
2626
"styled-components": "^6.1.13",
2727
"typescript": "^4.9.5",
28+
"val-i18n": "^0.1.13",
29+
"val-i18n-react": "^0.1.5",
2830
"web-vitals": "^2.1.4"
2931
},
3032
"scripts": {

‎webview-ui/src/App.tsx

+22-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,26 @@ import SettingsView from "./components/settings/SettingsView"
77
import WelcomeView from "./components/welcome/WelcomeView"
88
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
99
import { vscode } from "./utils/vscode"
10+
import { I18nProvider } from "val-i18n-react"
11+
import { I18n } from "val-i18n"
12+
import { createI18n } from "./locales"
1013

1114
const AppContent = () => {
12-
const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState()
15+
const { didHydrateState, showWelcome, shouldShowAnnouncement, locale } = useExtensionState()
1316
const [showSettings, setShowSettings] = useState(false)
1417
const [showHistory, setShowHistory] = useState(false)
1518
const [showAnnouncement, setShowAnnouncement] = useState(false)
19+
const [i18n, setI18n] = useState<I18n | null>(null)
20+
21+
useEffect(() => {
22+
if (i18n) {
23+
i18n.dispose()
24+
}
25+
createI18n(locale).then(instance => {
26+
setI18n(instance)
27+
});
28+
return () => i18n?.dispose()
29+
}, [locale, i18n])
1630

1731
const handleMessage = useCallback((e: MessageEvent) => {
1832
const message: ExtensionMessage = e.data
@@ -49,8 +63,12 @@ const AppContent = () => {
4963
return null
5064
}
5165

66+
if (!i18n) {
67+
return null
68+
}
69+
5270
return (
53-
<>
71+
<I18nProvider i18n={i18n}>
5472
{showWelcome ? (
5573
<WelcomeView />
5674
) : (
@@ -71,14 +89,14 @@ const AppContent = () => {
7189
/>
7290
</>
7391
)}
74-
</>
92+
</I18nProvider>
7593
)
7694
}
7795

7896
const App = () => {
7997
return (
8098
<ExtensionStateContextProvider>
81-
<AppContent />
99+
<AppContent />
82100
</ExtensionStateContextProvider>
83101
)
84102
}

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

+6-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import BrowserSessionRow from "./BrowserSessionRow"
2323
import ChatRow from "./ChatRow"
2424
import ChatTextArea from "./ChatTextArea"
2525
import TaskHeader from "./TaskHeader"
26+
import { useTranslate } from "val-i18n-react"
2627

2728
interface ChatViewProps {
2829
isHidden: boolean
@@ -59,6 +60,7 @@ const ChatView = ({ isHidden, showHistoryView }: ChatViewProps) => {
5960
const disableAutoScrollRef = useRef(false)
6061
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
6162
const [isAtBottom, setIsAtBottom] = useState(false)
63+
const t = useTranslate();
6264

6365
// UI layout depends on the last 2 messages
6466
// (since it relies on the content of these messages, we are deep comparing. i.e. the button state after hitting button sets enableButtons to false, and this effect otherwise would have to true again even if messages didn't change
@@ -631,9 +633,9 @@ const ChatView = ({ isHidden, showHistoryView }: ChatViewProps) => {
631633
useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance
632634

633635
const placeholderText = useMemo(() => {
634-
const text = task ? "Type a message (@ to add context)..." : "Type your task here (@ to add context)..."
636+
const text = task ? t("typeMessage") : t("typeTask")
635637
return text
636-
}, [task])
638+
}, [task, t])
637639

638640
const itemContent = useCallback(
639641
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {
@@ -705,12 +707,9 @@ const ChatView = ({ isHidden, showHistoryView }: ChatViewProps) => {
705707
flexDirection: "column",
706708
}}>
707709
<div style={{ padding: "0 20px", flexShrink: 0 }}>
708-
<h2>What can I do for you?</h2>
710+
<h2>{t("whatCanIDo")}</h2>
709711
<p>
710-
I can handle complex software development tasks step-by-step. With tools that let me create
711-
& edit files, explore complex projects, use the browser, and execute terminal commands
712-
(after you grant permission), I can assist you in ways that go beyond code completion or
713-
tech support.
712+
{t("welcomeDesc")}
714713
</p>
715714
</div>
716715
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}

‎webview-ui/src/components/settings/SettingsView.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useExtensionState } from "../../context/ExtensionStateContext"
44
import { validateApiConfiguration, validateModelId } from "../../utils/validate"
55
import { vscode } from "../../utils/vscode"
66
import ApiOptions from "./ApiOptions"
7+
import { useTranslate } from "val-i18n-react"
78

89
const IS_DEV = false // FIXME: use flags when packaging
910

@@ -12,6 +13,7 @@ type SettingsViewProps = {
1213
}
1314

1415
const SettingsView = ({ onDone }: SettingsViewProps) => {
16+
const t = useTranslate()
1517
const {
1618
apiConfiguration,
1719
version,
@@ -79,7 +81,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
7981
marginBottom: "17px",
8082
paddingRight: 17,
8183
}}>
82-
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h3>
84+
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>{t("settings.title")}</h3>
8385
<VSCodeButton onClick={handleSubmit}>Done</VSCodeButton>
8486
</div>
8587
<div
@@ -101,32 +103,31 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
101103
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
102104
}
103105
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
104-
<span style={{ fontWeight: "500" }}>Custom Instructions</span>
106+
<span style={{ fontWeight: "500" }}>{t("settings.customInstructions")}</span>
105107
</VSCodeTextArea>
106108
<p
107109
style={{
108110
fontSize: "12px",
109111
marginTop: "5px",
110112
color: "var(--vscode-descriptionForeground)",
111113
}}>
112-
These instructions are added to the end of the system prompt sent with every request.
114+
{t("settings.customInstructionsDesc")}
113115
</p>
114116
</div>
115117

116118
<div style={{ marginBottom: 5 }}>
117119
<VSCodeCheckbox
118120
checked={alwaysAllowReadOnly}
119121
onChange={(e: any) => setAlwaysAllowReadOnly(e.target.checked)}>
120-
<span style={{ fontWeight: "500" }}>Always approve read-only operations</span>
122+
<span style={{ fontWeight: "500" }}>{t("settings.alwaysApprove")}</span>
121123
</VSCodeCheckbox>
122124
<p
123125
style={{
124126
fontSize: "12px",
125127
marginTop: "5px",
126128
color: "var(--vscode-descriptionForeground)",
127129
}}>
128-
When enabled, Cline will automatically view directory contents and read files without requiring
129-
you to click the Approve button.
130+
{t("settings.alwaysApproveDesc")}
130131
</p>
131132
</div>
132133

‎webview-ui/src/context/ExtensionStateContext.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface ExtensionStateContextType extends ExtensionState {
1717
theme: any
1818
openRouterModels: Record<string, ModelInfo>
1919
filePaths: string[]
20+
locale: "en" | "zh-cn"
2021
setApiConfiguration: (config: ApiConfiguration) => void
2122
setCustomInstructions: (value?: string) => void
2223
setAlwaysAllowReadOnly: (value: boolean) => void
@@ -39,6 +40,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
3940
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
4041
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
4142
})
43+
const [locale, setLocale] = useState<"en" | "zh-cn">("en")
4244

4345
const handleMessage = useCallback((event: MessageEvent) => {
4446
const message: ExtensionMessage = event.data
@@ -95,6 +97,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
9597
})
9698
break
9799
}
100+
case "locale": {
101+
setLocale(message.locale ?? "en")
102+
break
103+
}
98104
}
99105
}, [])
100106

@@ -111,6 +117,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
111117
theme,
112118
openRouterModels,
113119
filePaths,
120+
locale,
114121
setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })),
115122
setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
116123
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),

‎webview-ui/src/locales/en.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"whatCanIDo": "What can I do for you?",
3+
"welcomeDesc": "I can handle complex software development tasks step-by-step. With tools that let me create & edit files, explore complex projects, use the browser, and execute terminal commands (after you grant permission), I can assist you in ways that go beyond code completion or tech support.",
4+
"typeMessage": "Type a message (@ to add context)...",
5+
"typeTask": "Type your task here (@ to add context)...",
6+
"settings": {
7+
"title": "Settings",
8+
"customInstructions": "Custom Instructions",
9+
"customInstructionsDesc": "These instructions are added to the end of the system prompt sent with every request.",
10+
"alwaysApprove": "Always approve read-only operations",
11+
"alwaysApproveDesc": "When enabled, Cline will automatically view directory contents and read files without requiring you to click the Approve button."
12+
}
13+
}

‎webview-ui/src/locales/index.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { I18n, Locale } from "val-i18n";
2+
import en from "./en.json"
3+
import zhCN from "./zh-cn.json"
4+
5+
export const locales: Record<string, Locale> = {
6+
en: en,
7+
"zh-cn": zhCN,
8+
};
9+
10+
export const createI18n = async (locale: string) => {
11+
return I18n.preload(locale, (lang) => locales[lang]);
12+
};
13+

‎webview-ui/src/locales/zh-cn.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"whatCanIDo": "我能为您做些什么?",
3+
"welcomeDesc": "我可以逐步处理复杂的软件开发任务。借助创建和编辑文件、探索复杂项目、使用浏览器以及执行终端命令(在你授权后)的工具,我能够以超越代码补全或技术支持的方式协助你。",
4+
"typeMessage": "输入消息(@添加上下文)...",
5+
"typeTask": "在此处输入任务(@添加上下文)...",
6+
"settings": {
7+
"title": "设置",
8+
"customInstructions": "自定义指令",
9+
"customInstructionsDesc": "这些指令会被添加到随每一次请求发送的系统提示的末尾。",
10+
"alwaysApprove": "始终批准只读操作",
11+
"alwaysApproveDesc": "启用后,Cline 将自动查看目录内容和读取文件,而无需您点击“批准”按钮。"
12+
}
13+
}

0 commit comments

Comments
 (0)
Please sign in to comment.