Skip to content
Draft
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
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@ Use one of these standard type prefixes:
- Formatting is handled by Prettier. `npm run format` is optional; review its
changes before committing because it rewrites files across the repository.
- Linting is enforced by ESLint; run `npm run lint`.
- Keep user-facing strings, comments, and documentation in English.
- Keep comments and documentation in English.
- User-facing strings go through i18next, not JSX literals. Author the copy in
`src/renderer/src/locales/en/`, then translate it in the `zh-Hans` and
`zh-Hant` folders — `docs/i18n-glossary.md` is the binding terminology
reference, and the catalog-parity tests fail on a missing or inconsistent
translation. Keys are semantic paths (`general.appearance.title`), never the
English source text.

## Required Checks

Expand Down
143 changes: 143 additions & 0 deletions docs/i18n-glossary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Localization Glossary

The binding reference for the `zh-Hans` and `zh-Hant` message catalogs under
`src/renderer/src/locales/`. English (`en`) is the source language: every key is authored there
first, and the other catalogs translate it.

Traditional Chinese is a **separate translation**, not a character conversion of Simplified. The
software vocabulary genuinely differs (`file` is 文件 in Simplified but 檔案 in Traditional, where
文件 means _document_), so running a converter over `zh-Hans` produces wrong copy. Translate from the
English source and consult the tables below.

## Kept in English

Never translated, in any catalog:

`Open Science` (product name), `Claude`, `Codex`, `opencode`, `Agent`, `Notebook`, `MCP`, `ACP`,
`API`, `CLI`, `SSH`, `GitHub`, `Star`, `Discord`, `Python`, `Jupyter`, `token`, and all model names.

- `Agent` and `Notebook` are first-class feature names that appear in dense UI; the English terms
are shorter and less ambiguous than 智能体 / 笔记本. Explanatory prose may describe them in
Chinese, but the labels stay English.
- `token` stays English because `12k tokens` reads more clearly than 词元 or 令牌 in the context
indicator, and matches how the surrounding community writes it.
- The `Open Science` name is fixed by `docs/design.md`, but the home tagline beneath it **is**
translated.

## Core domain nouns

| en | zh-Hans | zh-Hant |
| ------------------ | ---------- | ---------- |
| project | 项目 | 專案 |
| session | 会话 | 會話 |
| conversation | 对话 | 對話 |
| workspace | 工作区 | 工作區 |
| message | 消息 | 訊息 |
| task | 任务 | 任務 |
| run | 运行 | 執行 |
| turn | 轮次 | 輪次 |
| agent framework | Agent 框架 | Agent 框架 |
| model | 模型 | 模型 |
| provider | 模型服务商 | 模型服務商 |
| subscription | 订阅 | 訂閱 |
| skill | 技能 | 技能 |
| connector | 连接器 | 連接器 |
| kernel | 内核 | 核心 |
| artifact | 产物 | 產物 |
| activity group | 活动分组 | 活動分組 |
| tool | 工具 | 工具 |
| compute host | 计算主机 | 運算主機 |
| runtime | 运行时 | 執行環境 |
| environment | 环境 | 環境 |
| preview | 预览 | 預覽 |
| reasoning effort | 推理强度 | 推理強度 |
| context | 上下文 | 上下文 |
| context compaction | 上下文压缩 | 上下文壓縮 |

## Simplified / Traditional divergences

The highest-risk table. A character converter gets most of these wrong.

| en | zh-Hans | zh-Hant |
| ------------------ | ------- | -------- |
| file | 文件 | 檔案 |
| document | 文档 | 文件 |
| folder | 文件夹 | 資料夾 |
| data | 数据 | 資料 |
| information | 信息 | 資訊 |
| software | 软件 | 軟體 |
| program | 程序 | 程式 |
| default | 默认 | 預設 |
| settings | 设置 | 設定 |
| network | 网络 | 網路 |
| cache | 缓存 | 快取 |
| process | 进程 | 行程 |
| thread | 线程 | 執行緒 |
| queue | 队列 | 佇列 |
| storage | 存储 | 儲存 |
| credential | 凭据 | 憑證 |
| log | 日志 | 記錄檔 |
| mirror | 镜像源 | 鏡像來源 |
| tray | 托盘 | 系統匣 |
| bookmark | 书签 | 書籤 |
| archive (verb) | 归档 | 封存 |
| approve / approval | 批准 | 核准 |

Note the `file` / `document` inversion: Traditional 文件 means what Simplified calls 文档. Getting
this pair backwards is the single most common failure in Simplified-to-Traditional conversion.

## Actions and states

| en | zh-Hans | zh-Hant |
| -------------------- | -------------- | -------------- |
| create / new | 新建 | 新增 |
| edit | 编辑 | 編輯 |
| rename | 重命名 | 重新命名 |
| delete | 删除 | 刪除 |
| retry | 重试 | 重試 |
| resume | 继续 | 繼續 |
| stop | 停止 | 停止 |
| cancel | 取消 | 取消 |
| install / uninstall | 安装 / 卸载 | 安裝 / 移除 |
| validate | 验证 | 驗證 |
| import / export | 导入 / 导出 | 匯入 / 匯出 |
| upload / download | 上传 / 下载 | 上傳 / 下載 |
| reveal in folder | 在文件夹中显示 | 在資料夾中顯示 |
| minimize to tray | 最小化到托盘 | 最小化至系統匣 |
| idle | 空闲 | 閒置 |
| running | 运行中 | 執行中 |
| waiting for approval | 等待批准 | 等待核准 |
| failed | 失败 | 失敗 |
| completed | 已完成 | 已完成 |
| pending | 待处理 | 待處理 |

## Interface chrome

| en | zh-Hans | zh-Hant |
| --------------------- | ------------------ | ------------------ |
| Home | 首页 | 首頁 |
| Onboarding | 初始设置 | 初始設定 |
| General | 通用 | 一般 |
| Appearance | 外观 | 外觀 |
| Theme | 主题 | 主題 |
| System / Light / Dark | 系统 / 浅色 / 深色 | 系統 / 淺色 / 深色 |
| Language | 语言 | 語言 |
| Notifications | 通知 | 通知 |
| Diagnostics | 诊断 | 診斷 |
| Permissions | 权限 | 權限 |
| Data root | 数据目录 | 資料目錄 |
| Command line tool | 命令行工具 | 命令列工具 |

## Style rules

- Full-width punctuation (`,。:;?`) in Chinese prose. Code, paths, and commands keep their
original half-width characters.
- One half-width space between Chinese and Latin script (`使用 Claude 模型`). No space between a
number and a Chinese unit that reads as one word (`5 分钟` takes the space; `12k` is not split).
- Second person is 你, never 您 — it matches the supportive, non-authoritative tone `docs/design.md`
asks for.
- Short labels (buttons, table headers, menu items) take no trailing period. Full sentences do.
- No exclamation points, per `docs/design.md`.
- Don't pad imperatives with 请. `Check the network` is 检查网络连接, not 请检查网络连接.
- Language names in the language picker are written in their own language and never translated:
`English`, `简体中文`, `繁體中文`. Only the `System` option follows the interface language.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 82 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"i18next": "^25.10.10",
"jsdom": "^29.1.1",
"lucide-react": "^1.23.0",
"motion": "^12.42.2",
Expand All @@ -115,6 +116,7 @@
"radix-ui": "^1.6.1",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react-i18next": "^16.6.6",
"react-resizable-panels": "^4.12.0",
"react-zoom-pan-pinch": "^4.0.3",
"saxes": "6.0.0",
Expand Down
3 changes: 1 addition & 2 deletions scripts/fix-electron-path.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-require-imports */
// Normalize node_modules/electron/path.txt so the electron binary resolves without a trailing
// newline. Different npmmirror CDNs / extraction paths have shipped this 32-byte file with an
// appended LF; electron's own index.js reads it via fs.readFileSync(pathFile, 'utf-8') without
Expand Down Expand Up @@ -30,5 +30,4 @@ if (normalized === raw) {

fs.writeFileSync(pathFile, normalized, 'utf-8')

// eslint-disable-next-line no-console
console.log('fix-electron-path: stripped trailing newline from node_modules/electron/path.txt')
7 changes: 6 additions & 1 deletion src/main/compute/job-notifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,12 @@ describe('emitJobNotification', () => {
const hostRepo: Pick<ComputeHostRepository, 'get'> = { get: vi.fn() }
const broadcast = vi.fn()

await emitJobNotification(job, { jobRepository: jobRepo, hostRepository: hostRepo, storageRoot, broadcast })
await emitJobNotification(job, {
jobRepository: jobRepo,
hostRepository: hostRepo,
storageRoot,
broadcast
})

// Neither update nor broadcast should be called
expect(mockUpdate).not.toHaveBeenCalled()
Expand Down
4 changes: 1 addition & 3 deletions src/main/settings/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,7 @@ describe('validate: provider dispatch', () => {
it('surfaces a 5xx error message from a bridge validation', async () => {
const fetchImpl = vi
.fn()
.mockResolvedValue(
new Response('{"error":{"message":"Gateway timeout"}}', { status: 502 })
)
.mockResolvedValue(new Response('{"error":{"message":"Gateway timeout"}}', { status: 502 }))

const result = await validateProvider(
{ type: 'custom', apiEndpoints: ['openai'], baseUrl: 'https://g/v1', key: 'k', model: 'm' },
Expand Down
4 changes: 3 additions & 1 deletion src/main/settings/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,9 @@ const validateProviderThroughLocalResponsesAdapter = async (
if (category !== 'ok') {
return toResult(category, {
status: response.status,
...(category === 'unknown' || category === 'server-error' ? { message: providerMessage } : {})
...(category === 'unknown' || category === 'server-error'
? { message: providerMessage }
: {})
})
}
if (!adapter.hasRequiredToolCall(bodyText)) {
Expand Down
3 changes: 1 addition & 2 deletions src/main/skills/net-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ export const netFetch: FetchLike = (url, init) =>
// probe, which reads the error body and aborts on timeout. Same proxy-honoring Chromium stack. A lazy
// arrow wrapper (like netFetch) so `net.fetch` is only read at call time — reading it eagerly at module
// load crashes any test whose electron mock omits `net` — while the method call preserves the receiver.
export const netFetchStandard = ((input, init) =>
net.fetch(input as string, init)) as typeof fetch
export const netFetchStandard = ((input, init) => net.fetch(input as string, init)) as typeof fetch
Loading
Loading