diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 789a5d3b74..fa72ccef3b 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -14,6 +14,7 @@ import { iife } from "@opencode-ai/util/iife" import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js" import { createStore, produce } from "solid-js/store" import { Link } from "@/components/link" +import { useLanguage } from "@/context/language" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "@/context/global-sync" import { usePlatform } from "@/context/platform" @@ -25,6 +26,7 @@ export function DialogConnectProvider(props: { provider: string }) { const globalSync = useGlobalSync() const globalSDK = useGlobalSDK() const platform = usePlatform() + const language = useLanguage() const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!) const methods = createMemo( () => @@ -44,6 +46,12 @@ export function DialogConnectProvider(props: { provider: string }) { const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined)) + const methodLabel = (value?: { type?: string; label?: string }) => { + if (!value) return "" + if (value.type === "api") return language.t("provider.connect.method.apiKey") + return value.label ?? "" + } + async function selectMethod(index: number) { const method = methods()[index] setStore( @@ -112,8 +120,8 @@ export function DialogConnectProvider(props: { provider: string }) { showToast({ variant: "success", icon: "circle-check", - title: `${provider().name} connected`, - description: `${provider().name} models are now available to use.`, + title: language.t("provider.connect.toast.connected.title", { provider: provider().name }), + description: language.t("provider.connect.toast.connected.description", { provider: provider().name }), }) } @@ -142,16 +150,18 @@ export function DialogConnectProvider(props: { provider: string }) {
- Login with Claude Pro/Max + {language.t("provider.connect.title.anthropicProMax")} - Connect {provider().name} + {language.t("provider.connect.title", { provider: provider().name })}
-
Select login method for {provider().name}.
+
+ {language.t("provider.connect.selectMethod", { provider: provider().name })} +
{ @@ -169,7 +179,7 @@ export function DialogConnectProvider(props: { provider: string }) {
- {i.label} + {methodLabel(i)}
)}
@@ -179,7 +189,7 @@ export function DialogConnectProvider(props: { provider: string }) {
- Authorization in progress... + {language.t("provider.connect.status.inProgress")}
@@ -187,7 +197,7 @@ export function DialogConnectProvider(props: { provider: string }) {
- Authorization failed: {store.error} + {language.t("provider.connect.status.failed", { error: store.error ?? "" })}
@@ -206,7 +216,7 @@ export function DialogConnectProvider(props: { provider: string }) { const apiKey = formData.get("apiKey") as string if (!apiKey?.trim()) { - setFormStore("error", "API key is required") + setFormStore("error", language.t("provider.connect.apiKey.required")) return } @@ -226,26 +236,20 @@ export function DialogConnectProvider(props: { provider: string }) {
+
{language.t("provider.connect.opencodeZen.line1")}
+
{language.t("provider.connect.opencodeZen.line2")}
- OpenCode Zen gives you access to a curated set of reliable optimized models for coding - agents. -
-
- With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more. -
-
- Visit{" "} + {language.t("provider.connect.opencodeZen.visit.prefix")} opencode.ai/zen - {" "} - to collect your API key. + + {language.t("provider.connect.opencodeZen.visit.suffix")}
- Enter your {provider().name} API key to connect your account and use {provider().name} models - in OpenCode. + {language.t("provider.connect.apiKey.description", { provider: provider().name })}
@@ -253,8 +257,8 @@ export function DialogConnectProvider(props: { provider: string }) {
@@ -292,7 +296,7 @@ export function DialogConnectProvider(props: { provider: string }) { const code = formData.get("code") as string if (!code?.trim()) { - setFormStore("error", "Authorization code is required") + setFormStore("error", language.t("provider.connect.oauth.code.required")) return } @@ -306,21 +310,22 @@ export function DialogConnectProvider(props: { provider: string }) { await complete() return } - setFormStore("error", "Invalid authorization code") + setFormStore("error", language.t("provider.connect.oauth.code.invalid")) } return (
- Visit this link to collect your authorization - code to connect your account and use {provider().name} models in OpenCode. + {language.t("provider.connect.oauth.code.visit.prefix")} + {language.t("provider.connect.oauth.code.visit.link")} + {language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
@@ -361,13 +366,20 @@ export function DialogConnectProvider(props: { provider: string }) { return (
- Visit this link and enter the code below to - connect your account and use {provider().name} models in OpenCode. + {language.t("provider.connect.oauth.auto.visit.prefix")} + {language.t("provider.connect.oauth.auto.visit.link")} + {language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
- +
- Waiting for authorization... + {language.t("provider.connect.status.waiting")}
) diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 9e100d5200..67a08c8292 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -95,12 +95,47 @@ export const dict = { "dialog.provider.viewAll": "View all providers", + "provider.connect.title": "Connect {{provider}}", + "provider.connect.title.anthropicProMax": "Login with Claude Pro/Max", + "provider.connect.selectMethod": "Select login method for {{provider}}.", + "provider.connect.method.apiKey": "API key", + "provider.connect.status.inProgress": "Authorization in progress...", + "provider.connect.status.waiting": "Waiting for authorization...", + "provider.connect.status.failed": "Authorization failed: {{error}}", + "provider.connect.apiKey.description": + "Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.", + "provider.connect.apiKey.label": "{{provider}} API key", + "provider.connect.apiKey.placeholder": "API key", + "provider.connect.apiKey.required": "API key is required", + "provider.connect.opencodeZen.line1": + "OpenCode Zen gives you access to a curated set of reliable optimized models for coding agents.", + "provider.connect.opencodeZen.line2": + "With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.", + "provider.connect.opencodeZen.visit.prefix": "Visit ", + "provider.connect.opencodeZen.visit.suffix": " to collect your API key.", + "provider.connect.oauth.code.visit.prefix": "Visit ", + "provider.connect.oauth.code.visit.link": "this link", + "provider.connect.oauth.code.visit.suffix": + " to collect your authorization code to connect your account and use {{provider}} models in OpenCode.", + "provider.connect.oauth.code.label": "{{method}} authorization code", + "provider.connect.oauth.code.placeholder": "Authorization code", + "provider.connect.oauth.code.required": "Authorization code is required", + "provider.connect.oauth.code.invalid": "Invalid authorization code", + "provider.connect.oauth.auto.visit.prefix": "Visit ", + "provider.connect.oauth.auto.visit.link": "this link", + "provider.connect.oauth.auto.visit.suffix": + " and enter the code below to connect your account and use {{provider}} models in OpenCode.", + "provider.connect.oauth.auto.confirmationCode": "Confirmation code", + "provider.connect.toast.connected.title": "{{provider}} connected", + "provider.connect.toast.connected.description": "{{provider}} models are now available to use.", + "model.tag.free": "Free", "model.tag.latest": "Latest", "common.search.placeholder": "Search", "common.loading": "Loading", "common.cancel": "Cancel", + "common.submit": "Submit", "common.save": "Save", "common.saving": "Saving...", "common.default": "Default", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 28a39612a5..ef2be2f377 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -99,12 +99,42 @@ export const dict = { "dialog.provider.viewAll": "查看全部提供商", + "provider.connect.title": "连接 {{provider}}", + "provider.connect.title.anthropicProMax": "使用 Claude Pro/Max 登录", + "provider.connect.selectMethod": "选择 {{provider}} 的登录方式。", + "provider.connect.method.apiKey": "API 密钥", + "provider.connect.status.inProgress": "正在授权...", + "provider.connect.status.waiting": "等待授权...", + "provider.connect.status.failed": "授权失败: {{error}}", + "provider.connect.apiKey.description": "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。", + "provider.connect.apiKey.label": "{{provider}} API 密钥", + "provider.connect.apiKey.placeholder": "API 密钥", + "provider.connect.apiKey.required": "API 密钥为必填项", + "provider.connect.opencodeZen.line1": "OpenCode Zen 为你提供一组精选的可靠优化模型,用于代码智能体。", + "provider.connect.opencodeZen.line2": "只需一个 API 密钥,你就能使用 Claude、GPT、Gemini、GLM 等模型。", + "provider.connect.opencodeZen.visit.prefix": "访问 ", + "provider.connect.opencodeZen.visit.suffix": " 获取你的 API 密钥。", + "provider.connect.oauth.code.visit.prefix": "访问 ", + "provider.connect.oauth.code.visit.link": "此链接", + "provider.connect.oauth.code.visit.suffix": " 获取授权码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。", + "provider.connect.oauth.code.label": "{{method}} 授权码", + "provider.connect.oauth.code.placeholder": "授权码", + "provider.connect.oauth.code.required": "授权码为必填项", + "provider.connect.oauth.code.invalid": "授权码无效", + "provider.connect.oauth.auto.visit.prefix": "访问 ", + "provider.connect.oauth.auto.visit.link": "此链接", + "provider.connect.oauth.auto.visit.suffix": " 并输入以下代码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。", + "provider.connect.oauth.auto.confirmationCode": "确认码", + "provider.connect.toast.connected.title": "{{provider}} 已连接", + "provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。", + "model.tag.free": "免费", "model.tag.latest": "最新", "common.search.placeholder": "搜索", "common.loading": "加载中", "common.cancel": "取消", + "common.submit": "提交", "common.save": "保存", "common.saving": "保存中...", "common.default": "默认", diff --git a/specs/06-app-i18n-audit.md b/specs/06-app-i18n-audit.md index de77e74416..e47dac30df 100644 --- a/specs/06-app-i18n-audit.md +++ b/specs/06-app-i18n-audit.md @@ -9,8 +9,8 @@ This report documents the remaining user-facing strings in `packages/app/src` th ## Current State - The app uses `useLanguage().t("...")` with dictionaries in `packages/app/src/i18n/en.ts` and `packages/app/src/i18n/zh.ts`. -- Recent progress (already translated): `packages/app/src/pages/home.tsx`, `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/session.tsx`, `packages/app/src/components/prompt-input.tsx` (plus new keys added in both dictionaries). -- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (285 keys each; no missing or extra keys). +- Recent progress (already translated): `packages/app/src/pages/home.tsx`, `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/session.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/app/src/components/dialog-connect-provider.tsx` (plus new keys added in both dictionaries). +- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (314 keys each; no missing or extra keys). ## Methodology @@ -71,24 +71,11 @@ Completed (2026-01-20): File: `packages/app/src/components/dialog-connect-provider.tsx` -This flow is copy-heavy and user-facing. +Completed (2026-01-20): -**Representative untranslated strings** -- Login method label: "API key" -- Status text: "Authorization in progress...", "Waiting for authorization..." -- Validation: "API key is required", "Authorization code is required", "Invalid authorization code" -- Field labels/placeholders: "Confirmation code", placeholder "API key", placeholder "Authorization code" -- Instructional text: - - "Visit this link ..." - - Provider-specific guidance and OpenCode Zen onboarding paragraphs -- Buttons: "Submit" -- Success toast: - - "{provider} connected" - - "{provider} models are now available to use." - -**Recommendation:** -- Add a `provider.connect.*` namespace. -- Consider adding shared common keys like `common.submit` if it is used elsewhere. +- Localized all user-visible copy via `provider.connect.*` keys (titles, statuses, validations, instructions, OpenCode Zen onboarding). +- Added `common.submit` and used it for both API + OAuth submit buttons. +- Localized the success toast via `provider.connect.toast.connected.*`. ### 4) Session Header (Share/Publish UI) @@ -250,12 +237,11 @@ This is only thrown in DEV and is more of a developer diagnostic. Optional to tr ## Prioritized Implementation Plan -1. `packages/app/src/components/dialog-connect-provider.tsx` -2. `packages/app/src/components/session/session-header.tsx` -3. `packages/app/src/pages/error.tsx` -4. `packages/app/src/components/session/session-new-view.tsx` -5. `packages/app/src/components/session-context-usage.tsx` + locale formatting improvements (also `packages/app/src/components/session/session-context-tab.tsx`) -6. Small stragglers: +1. `packages/app/src/components/session/session-header.tsx` +2. `packages/app/src/pages/error.tsx` +3. `packages/app/src/components/session/session-new-view.tsx` +4. `packages/app/src/components/session-context-usage.tsx` + locale formatting improvements (also `packages/app/src/components/session/session-context-tab.tsx`) +5. Small stragglers: - `packages/app/src/components/session-lsp-indicator.tsx` - `packages/app/src/components/session/session-sortable-tab.tsx` - `packages/app/src/components/titlebar.tsx` @@ -264,7 +250,7 @@ This is only thrown in DEV and is more of a developer diagnostic. Optional to tr - `packages/app/src/context/global-sync.tsx` - `packages/app/src/context/file.tsx` + `packages/app/src/context/local.tsx` - `packages/app/src/utils/prompt.ts` -7. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`). +6. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`). ## Suggested Key Naming Conventions @@ -287,7 +273,6 @@ Pages: - `packages/app/src/pages/error.tsx` Components: -- `packages/app/src/components/dialog-connect-provider.tsx` - `packages/app/src/components/session/session-header.tsx` - `packages/app/src/components/session/session-new-view.tsx` - `packages/app/src/components/session-context-usage.tsx`