Skip to content

Commit 7a359ff

Browse files
committed
wip(app): i18n
1 parent 835fea6 commit 7a359ff

4 files changed

Lines changed: 124 additions & 62 deletions

File tree

packages/app/src/components/dialog-connect-provider.tsx

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { iife } from "@opencode-ai/util/iife"
1414
import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
1515
import { createStore, produce } from "solid-js/store"
1616
import { Link } from "@/components/link"
17+
import { useLanguage } from "@/context/language"
1718
import { useGlobalSDK } from "@/context/global-sdk"
1819
import { useGlobalSync } from "@/context/global-sync"
1920
import { usePlatform } from "@/context/platform"
@@ -25,6 +26,7 @@ export function DialogConnectProvider(props: { provider: string }) {
2526
const globalSync = useGlobalSync()
2627
const globalSDK = useGlobalSDK()
2728
const platform = usePlatform()
29+
const language = useLanguage()
2830
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
2931
const methods = createMemo(
3032
() =>
@@ -44,6 +46,12 @@ export function DialogConnectProvider(props: { provider: string }) {
4446

4547
const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined))
4648

49+
const methodLabel = (value?: { type?: string; label?: string }) => {
50+
if (!value) return ""
51+
if (value.type === "api") return language.t("provider.connect.method.apiKey")
52+
return value.label ?? ""
53+
}
54+
4755
async function selectMethod(index: number) {
4856
const method = methods()[index]
4957
setStore(
@@ -112,8 +120,8 @@ export function DialogConnectProvider(props: { provider: string }) {
112120
showToast({
113121
variant: "success",
114122
icon: "circle-check",
115-
title: `${provider().name} connected`,
116-
description: `${provider().name} models are now available to use.`,
123+
title: language.t("provider.connect.toast.connected.title", { provider: provider().name }),
124+
description: language.t("provider.connect.toast.connected.description", { provider: provider().name }),
117125
})
118126
}
119127

@@ -142,16 +150,18 @@ export function DialogConnectProvider(props: { provider: string }) {
142150
<div class="text-16-medium text-text-strong">
143151
<Switch>
144152
<Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
145-
Login with Claude Pro/Max
153+
{language.t("provider.connect.title.anthropicProMax")}
146154
</Match>
147-
<Match when={true}>Connect {provider().name}</Match>
155+
<Match when={true}>{language.t("provider.connect.title", { provider: provider().name })}</Match>
148156
</Switch>
149157
</div>
150158
</div>
151159
<div class="px-2.5 pb-10 flex flex-col gap-6">
152160
<Switch>
153161
<Match when={store.methodIndex === undefined}>
154-
<div class="text-14-regular text-text-base">Select login method for {provider().name}.</div>
162+
<div class="text-14-regular text-text-base">
163+
{language.t("provider.connect.selectMethod", { provider: provider().name })}
164+
</div>
155165
<div class="">
156166
<List
157167
ref={(ref) => {
@@ -169,7 +179,7 @@ export function DialogConnectProvider(props: { provider: string }) {
169179
<div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
170180
<div class="w-2.5 h-0.5 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
171181
</div>
172-
<span>{i.label}</span>
182+
<span>{methodLabel(i)}</span>
173183
</div>
174184
)}
175185
</List>
@@ -179,15 +189,15 @@ export function DialogConnectProvider(props: { provider: string }) {
179189
<div class="text-14-regular text-text-base">
180190
<div class="flex items-center gap-x-2">
181191
<Spinner />
182-
<span>Authorization in progress...</span>
192+
<span>{language.t("provider.connect.status.inProgress")}</span>
183193
</div>
184194
</div>
185195
</Match>
186196
<Match when={store.state === "error"}>
187197
<div class="text-14-regular text-text-base">
188198
<div class="flex items-center gap-x-2">
189199
<Icon name="circle-ban-sign" class="text-icon-critical-base" />
190-
<span>Authorization failed: {store.error}</span>
200+
<span>{language.t("provider.connect.status.failed", { error: store.error ?? "" })}</span>
191201
</div>
192202
</div>
193203
</Match>
@@ -206,7 +216,7 @@ export function DialogConnectProvider(props: { provider: string }) {
206216
const apiKey = formData.get("apiKey") as string
207217

208218
if (!apiKey?.trim()) {
209-
setFormStore("error", "API key is required")
219+
setFormStore("error", language.t("provider.connect.apiKey.required"))
210220
return
211221
}
212222

@@ -226,43 +236,37 @@ export function DialogConnectProvider(props: { provider: string }) {
226236
<Switch>
227237
<Match when={provider().id === "opencode"}>
228238
<div class="flex flex-col gap-4">
239+
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line1")}</div>
240+
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line2")}</div>
229241
<div class="text-14-regular text-text-base">
230-
OpenCode Zen gives you access to a curated set of reliable optimized models for coding
231-
agents.
232-
</div>
233-
<div class="text-14-regular text-text-base">
234-
With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.
235-
</div>
236-
<div class="text-14-regular text-text-base">
237-
Visit{" "}
242+
{language.t("provider.connect.opencodeZen.visit.prefix")}
238243
<Link href="https://opencode.ai/zen" tabIndex={-1}>
239244
opencode.ai/zen
240-
</Link>{" "}
241-
to collect your API key.
245+
</Link>
246+
{language.t("provider.connect.opencodeZen.visit.suffix")}
242247
</div>
243248
</div>
244249
</Match>
245250
<Match when={true}>
246251
<div class="text-14-regular text-text-base">
247-
Enter your {provider().name} API key to connect your account and use {provider().name} models
248-
in OpenCode.
252+
{language.t("provider.connect.apiKey.description", { provider: provider().name })}
249253
</div>
250254
</Match>
251255
</Switch>
252256
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
253257
<TextField
254258
autofocus
255259
type="text"
256-
label={`${provider().name} API key`}
257-
placeholder="API key"
260+
label={language.t("provider.connect.apiKey.label", { provider: provider().name })}
261+
placeholder={language.t("provider.connect.apiKey.placeholder")}
258262
name="apiKey"
259263
value={formStore.value}
260264
onChange={setFormStore.bind(null, "value")}
261265
validationState={formStore.error ? "invalid" : undefined}
262266
error={formStore.error}
263267
/>
264268
<Button class="w-auto" type="submit" size="large" variant="primary">
265-
Submit
269+
{language.t("common.submit")}
266270
</Button>
267271
</form>
268272
</div>
@@ -292,7 +296,7 @@ export function DialogConnectProvider(props: { provider: string }) {
292296
const code = formData.get("code") as string
293297

294298
if (!code?.trim()) {
295-
setFormStore("error", "Authorization code is required")
299+
setFormStore("error", language.t("provider.connect.oauth.code.required"))
296300
return
297301
}
298302

@@ -306,29 +310,30 @@ export function DialogConnectProvider(props: { provider: string }) {
306310
await complete()
307311
return
308312
}
309-
setFormStore("error", "Invalid authorization code")
313+
setFormStore("error", language.t("provider.connect.oauth.code.invalid"))
310314
}
311315

312316
return (
313317
<div class="flex flex-col gap-6">
314318
<div class="text-14-regular text-text-base">
315-
Visit <Link href={store.authorization!.url}>this link</Link> to collect your authorization
316-
code to connect your account and use {provider().name} models in OpenCode.
319+
{language.t("provider.connect.oauth.code.visit.prefix")}
320+
<Link href={store.authorization!.url}>{language.t("provider.connect.oauth.code.visit.link")}</Link>
321+
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
317322
</div>
318323
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
319324
<TextField
320325
autofocus
321326
type="text"
322-
label={`${method()?.label} authorization code`}
323-
placeholder="Authorization code"
327+
label={language.t("provider.connect.oauth.code.label", { method: method()?.label ?? "" })}
328+
placeholder={language.t("provider.connect.oauth.code.placeholder")}
324329
name="code"
325330
value={formStore.value}
326331
onChange={setFormStore.bind(null, "value")}
327332
validationState={formStore.error ? "invalid" : undefined}
328333
error={formStore.error}
329334
/>
330335
<Button class="w-auto" type="submit" size="large" variant="primary">
331-
Submit
336+
{language.t("common.submit")}
332337
</Button>
333338
</form>
334339
</div>
@@ -361,13 +366,20 @@ export function DialogConnectProvider(props: { provider: string }) {
361366
return (
362367
<div class="flex flex-col gap-6">
363368
<div class="text-14-regular text-text-base">
364-
Visit <Link href={store.authorization!.url}>this link</Link> and enter the code below to
365-
connect your account and use {provider().name} models in OpenCode.
369+
{language.t("provider.connect.oauth.auto.visit.prefix")}
370+
<Link href={store.authorization!.url}>{language.t("provider.connect.oauth.auto.visit.link")}</Link>
371+
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
366372
</div>
367-
<TextField label="Confirmation code" class="font-mono" value={code()} readOnly copyable />
373+
<TextField
374+
label={language.t("provider.connect.oauth.auto.confirmationCode")}
375+
class="font-mono"
376+
value={code()}
377+
readOnly
378+
copyable
379+
/>
368380
<div class="text-14-regular text-text-base flex items-center gap-4">
369381
<Spinner />
370-
<span>Waiting for authorization...</span>
382+
<span>{language.t("provider.connect.status.waiting")}</span>
371383
</div>
372384
</div>
373385
)

packages/app/src/i18n/en.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,47 @@ export const dict = {
9595

9696
"dialog.provider.viewAll": "View all providers",
9797

98+
"provider.connect.title": "Connect {{provider}}",
99+
"provider.connect.title.anthropicProMax": "Login with Claude Pro/Max",
100+
"provider.connect.selectMethod": "Select login method for {{provider}}.",
101+
"provider.connect.method.apiKey": "API key",
102+
"provider.connect.status.inProgress": "Authorization in progress...",
103+
"provider.connect.status.waiting": "Waiting for authorization...",
104+
"provider.connect.status.failed": "Authorization failed: {{error}}",
105+
"provider.connect.apiKey.description":
106+
"Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.",
107+
"provider.connect.apiKey.label": "{{provider}} API key",
108+
"provider.connect.apiKey.placeholder": "API key",
109+
"provider.connect.apiKey.required": "API key is required",
110+
"provider.connect.opencodeZen.line1":
111+
"OpenCode Zen gives you access to a curated set of reliable optimized models for coding agents.",
112+
"provider.connect.opencodeZen.line2":
113+
"With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.",
114+
"provider.connect.opencodeZen.visit.prefix": "Visit ",
115+
"provider.connect.opencodeZen.visit.suffix": " to collect your API key.",
116+
"provider.connect.oauth.code.visit.prefix": "Visit ",
117+
"provider.connect.oauth.code.visit.link": "this link",
118+
"provider.connect.oauth.code.visit.suffix":
119+
" to collect your authorization code to connect your account and use {{provider}} models in OpenCode.",
120+
"provider.connect.oauth.code.label": "{{method}} authorization code",
121+
"provider.connect.oauth.code.placeholder": "Authorization code",
122+
"provider.connect.oauth.code.required": "Authorization code is required",
123+
"provider.connect.oauth.code.invalid": "Invalid authorization code",
124+
"provider.connect.oauth.auto.visit.prefix": "Visit ",
125+
"provider.connect.oauth.auto.visit.link": "this link",
126+
"provider.connect.oauth.auto.visit.suffix":
127+
" and enter the code below to connect your account and use {{provider}} models in OpenCode.",
128+
"provider.connect.oauth.auto.confirmationCode": "Confirmation code",
129+
"provider.connect.toast.connected.title": "{{provider}} connected",
130+
"provider.connect.toast.connected.description": "{{provider}} models are now available to use.",
131+
98132
"model.tag.free": "Free",
99133
"model.tag.latest": "Latest",
100134

101135
"common.search.placeholder": "Search",
102136
"common.loading": "Loading",
103137
"common.cancel": "Cancel",
138+
"common.submit": "Submit",
104139
"common.save": "Save",
105140
"common.saving": "Saving...",
106141
"common.default": "Default",

packages/app/src/i18n/zh.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,42 @@ export const dict = {
9999

100100
"dialog.provider.viewAll": "查看全部提供商",
101101

102+
"provider.connect.title": "连接 {{provider}}",
103+
"provider.connect.title.anthropicProMax": "使用 Claude Pro/Max 登录",
104+
"provider.connect.selectMethod": "选择 {{provider}} 的登录方式。",
105+
"provider.connect.method.apiKey": "API 密钥",
106+
"provider.connect.status.inProgress": "正在授权...",
107+
"provider.connect.status.waiting": "等待授权...",
108+
"provider.connect.status.failed": "授权失败: {{error}}",
109+
"provider.connect.apiKey.description": "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
110+
"provider.connect.apiKey.label": "{{provider}} API 密钥",
111+
"provider.connect.apiKey.placeholder": "API 密钥",
112+
"provider.connect.apiKey.required": "API 密钥为必填项",
113+
"provider.connect.opencodeZen.line1": "OpenCode Zen 为你提供一组精选的可靠优化模型,用于代码智能体。",
114+
"provider.connect.opencodeZen.line2": "只需一个 API 密钥,你就能使用 Claude、GPT、Gemini、GLM 等模型。",
115+
"provider.connect.opencodeZen.visit.prefix": "访问 ",
116+
"provider.connect.opencodeZen.visit.suffix": " 获取你的 API 密钥。",
117+
"provider.connect.oauth.code.visit.prefix": "访问 ",
118+
"provider.connect.oauth.code.visit.link": "此链接",
119+
"provider.connect.oauth.code.visit.suffix": " 获取授权码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。",
120+
"provider.connect.oauth.code.label": "{{method}} 授权码",
121+
"provider.connect.oauth.code.placeholder": "授权码",
122+
"provider.connect.oauth.code.required": "授权码为必填项",
123+
"provider.connect.oauth.code.invalid": "授权码无效",
124+
"provider.connect.oauth.auto.visit.prefix": "访问 ",
125+
"provider.connect.oauth.auto.visit.link": "此链接",
126+
"provider.connect.oauth.auto.visit.suffix": " 并输入以下代码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。",
127+
"provider.connect.oauth.auto.confirmationCode": "确认码",
128+
"provider.connect.toast.connected.title": "{{provider}} 已连接",
129+
"provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。",
130+
102131
"model.tag.free": "免费",
103132
"model.tag.latest": "最新",
104133

105134
"common.search.placeholder": "搜索",
106135
"common.loading": "加载中",
107136
"common.cancel": "取消",
137+
"common.submit": "提交",
108138
"common.save": "保存",
109139
"common.saving": "保存中...",
110140
"common.default": "默认",

0 commit comments

Comments
 (0)