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
1 change: 1 addition & 0 deletions .claude/rules/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ parts of `packages/presentation/ui` (`chat`/`shell`) and `packages/client/workbe
- **React Compiler: `ref={…}` takes a plain identifier only.** A member expression there (`ref={bag.setHandle}`) makes the compiler infer the whole object as a ref and reject every other render-time read of it ("Cannot access refs during render"). Destructure the ref setter into its own binding (`const { setHandle: paneRef, ...rest } = useSomething(…)`) and pass ref callbacks between components as standalone props, never on a bag object.
- **Keyboard ownership.** Focus-local behavior (text editing/submission, menus, dialogs, drag-and-drop, terminal input) stays with the owning component or dependency. Commands that must work independently of focus register through `packages/presentation/ui/src/keyboard` with a ref to the surface they serve; a binding is active only while that owner is connected and outside `inert`, `aria-hidden="true"`, and Base UI's inert markers, so the DOM owns overlay precedence. Each renderer entry sets its platform synchronously before rendering; the shared provider only installs the listeners. Primary/Alt application chords use capture, while bare contextual keys and `Escape` use bubble so local handlers act first. The registry rejects IME composition/`Process`, extra modifiers, and repeats; bindings claim an event only after handling it. Complex local widgets may mark their root with `data-keyboard-shortcut-local`; the registry does not read that marker automatically, so bindings that must yield there opt in through `isKeyboardShortcutLocalTarget`. Palette/panel commands deliberately remain global, while browser history chords stay native in webview and are registered only by the desktop shell. The coss-ui `SidebarProvider` is still deliberately not mounted because its own global ⌘/Ctrl+B would duplicate the registry.
- **i18n.** User-facing strings in `packages/presentation/ui`/`packages/client/workbench` go through `use-intl` (`useTranslations('workbench.…')`; dynamic keys within a namespace, like `t(kind)`, are fine). `packages/presentation/i18n/src/locales/zh-cn.ts` is the type source — add new keys there first; `en.ts` must `satisfies LocaleMessages`.
- **CJK typography in locale strings.** Chinese text uses full-width punctuation (`,` `;` `?` `。` `()` `「」` `…`) — never half-width (`,;?.()""...`) adjacent to CJK characters. Latin letters and digits within Chinese strings stay half-width, with a space at every CJK↔Latin/digit boundary (`API 直连`, not `API直连` or `API直连`). `http(s)://` and similar technical notation keep half-width parentheses. The ellipsis is always `…` (U+2026), never three dots `...` — in both `zh-cn.ts` and `en.ts`. Brand names that are entirely Latin (`xAI (Grok)`) use half-width parentheses with a preceding space, matching `en.ts`.
- **Renderer ownership boundaries.** Apps own entries and platform construction; `packages/client/workbench` owns data-plane runtime; `packages/presentation/ui` owns presentation.
- `apps/desktop` may read from `SystemBridge`, integrate native chrome/window behavior, and construct desktop transport. Pass system values down as props; do not keep shared UI in desktop for a single IPC-derived value.
- `apps/webview` may construct browser transport and browser entry/root only. It must not host shared providers or desktop-consumed shells.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function translateKey(key: string): string {
}

vi.mock('use-intl', () => ({
useLocale: () => 'en',
useTranslations: () => translateKey,
}));

Expand Down
10 changes: 8 additions & 2 deletions packages/client/workbench/src/settings/providers/add-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { ChevronLeftIcon } from 'lucide-react';
import { useState } from 'react';
import type { Control, FieldValues, Path } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { useTranslations } from 'use-intl';
import { useLocale, useTranslations } from 'use-intl';
import { z } from 'zod';
import type { AgentRuntimeOnboarding } from '../../agent-runtime/onboarding';
import type { ModelSources } from './model-selection';
Expand Down Expand Up @@ -137,11 +137,17 @@ export function ServiceCatalogView({
linkCodeGatewayAvailable?: boolean;
}): React.ReactNode {
const t = useTranslations('settings.providers');
const locale = useLocale();
// tracking-widest (0.1em) inflates Latin glyphs beside CJK, making e.g. "API" in
// "API 直连" read as full-width. CJK glyphs carry natural sidebearings, so skip it.
const groupLabelTracking = locale.startsWith('zh') ? '' : ' tracking-widest';
return (
<div className="flex min-w-0 flex-1 flex-col gap-4">
{GROUPS.map((group) => (
<div key={group} className="flex flex-col gap-2">
<span className="font-semibold text-2xs text-muted-foreground uppercase tracking-widest">
<span
className={`font-semibold text-2xs text-muted-foreground uppercase${groupLabelTracking}`}
>
Comment thread
lucas77778 marked this conversation as resolved.
{t(`group.${group}`)}
</span>
<div className="grid grid-cols-2 gap-2 xl:grid-cols-3">
Expand Down
2 changes: 1 addition & 1 deletion packages/presentation/i18n/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ export const en = {
settings: {
title: 'Settings',
back: 'Back',
searchPlaceholder: 'Search settings...',
searchPlaceholder: 'Search settings',
searchNoResults: 'No matching settings.',
groups: {
personal: 'Personal',
Expand Down
30 changes: 15 additions & 15 deletions packages/presentation/i18n/src/locales/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ export const zhCN = {
settings: {
title: '设置',
back: '返回',
searchPlaceholder: '搜索设置...',
searchPlaceholder: '搜索设置',
searchNoResults: '没有匹配的设置。',
groups: {
personal: '个人',
Expand Down Expand Up @@ -1002,7 +1002,7 @@ export const zhCN = {
},
agents: {
title: '智能体',
hint: '启用状态与运行时;账号与模型的绑定在 Providers 页管理。',
hint: '启用状态与运行时账号与模型的绑定在 Providers 页管理。',
enabled: '启用',
followCli: '默认 · 跟随 CLI 登录',
translated: '经转换',
Expand All @@ -1018,7 +1018,7 @@ export const zhCN = {
},
providers: {
title: '账号与 Provider',
hint: '把订阅、AI 网关或自定义端点接入你的智能体;一个账号可接入多个智能体,每个智能体同一时刻使用一个账号。',
hint: '把订阅、AI 网关或自定义端点接入你的智能体一个账号可接入多个智能体每个智能体同一时刻使用一个账号。',
searchPlaceholder: '搜索账号…',
addAccount: '添加账号',
orderHint: '拖动账号调整优先级;新建任务默认使用首个兼容账号的首个模型。',
Expand Down Expand Up @@ -1073,11 +1073,11 @@ export const zhCN = {
translateNote: '本地网关将 Anthropic 协议转为 OpenAI Chat',
unavailableOauth: '仅可接入 {agent}',
unavailableProtocol: '端点协议与此智能体不兼容',
unavailableEndpointIncomplete: '端点信息不完整,请补全账号设置',
unavailableEndpointIncomplete: '端点信息不完整请补全账号设置',
configPreview: 'config.json 片段 · 此账号写入的内容',
configPreviewEmpty: '// 尚未接入任何智能体',
remove: '移除账号',
removeTitle: '移除「{label}」?',
removeTitle: '移除「{label}」',
removeInUse: '{agents} 将回退为跟随 CLI 登录。',
removeHint: '此账号尚未接入任何智能体。',
cancel: '取消',
Expand All @@ -1093,19 +1093,19 @@ export const zhCN = {
'chatgpt-sub': 'ChatGPT 订阅',
'anthropic-api': 'Anthropic API',
'openai-api': 'OpenAI API',
xai: 'xAI(Grok)',
xai: 'xAI (Grok)',
deepseek: 'DeepSeek',
stepfun: '阶跃星辰 StepFun',
openrouter: 'OpenRouter',
'vercel-gateway': 'Vercel AI Gateway',
'cloudflare-gateway': 'Cloudflare AI Gateway',
'linkcode-gateway': 'LinkCode Gateway',
'cloudflare-anthropic': 'Cloudflare AI Gateway(Anthropic)',
'cloudflare-anthropic': 'Cloudflare AI Gateway (Anthropic)',
custom: '自定义端点',
},
serviceHint: {
'claude-sub': 'Claude Code 登录,Pro / Max 计划',
'chatgpt-sub': 'Codex 登录,Plus / Pro 计划',
'claude-sub': 'Claude Code 登录Pro / Max 计划',
'chatgpt-sub': 'Codex 登录Plus / Pro 计划',
'anthropic-api': 'console.anthropic.com 的密钥',
'openai-api': 'platform.openai.com 的密钥',
xai: 'console.x.ai 的密钥',
Expand All @@ -1114,20 +1114,20 @@ export const zhCN = {
'linkcode-gateway': '无需自带 API 密钥,直接使用 LinkCode 额度',
openrouter: '聚合网关',
'vercel-gateway': '服务端翻译网关',
'cloudflare-gateway': '兼容网关,需 Account / Gateway ID',
'cloudflare-anthropic': '透传网关,需 Anthropic 密钥与 Account / Gateway ID',
'cloudflare-gateway': '兼容网关需 Account / Gateway ID',
'cloudflare-anthropic': '透传网关需 Anthropic 密钥与 Account / Gateway ID',
custom: '任意 Base URL + 协议',
},
oauthEditHint: '订阅凭证跟随 CLI 登录;这里只能修改账号名称。',
oauthEditHint: '订阅凭证跟随 CLI 登录这里只能修改账号名称。',
form: {
label: '名称',
model: '默认模型(可选)',
model: '默认模型(可选)',
submit: '添加账号',
save: '保存更改',
credentialType: '类型',
secret: '密钥 / 令牌',
baseUrl: '端点 URL(可选)',
protocol: '协议(可选)',
baseUrl: '端点 URL(可选)',
protocol: '协议(可选)',
protocolNone: '默认',
},
},
Expand Down
Loading