Skip to content

feat(i18n): localize Connectors and Tools in zh-CN - #1717

Open
lilyshen0722 wants to merge 10 commits into
mainfrom
kai/task036
Open

lilyshen0722 wants to merge 10 commits into
mainfrom
kai/task036

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add explicit English and native Simplified Chinese copy for every connectors.* and tools.* key used by the v2 pages
  • localize relative times, row when-column wrappers, linked/fallback labels, gate timestamps, budget windows, and trail timestamps
  • disambiguate Slack confirmation action keys so row and aside labels remain distinct
  • add locale coverage and raw-English-template invariants
  • include a real-browser 1200×1000 zh-CN evidence capture with connected and pending rows visible
  • manifest descriptions remain server-owned and are tracked separately in TASK-038

Verification

  • npx jest --watchAll=false --runInBand src/v2/__tests__/v2-i18n-invariants.test.ts
  • npx jest --watchAll=false --runInBand src/v2/__tests__/V2ConnectorsPage.test.tsx src/v2/__tests__/V2ConnectorTools.test.tsx
  • npm run build

UX Lead review and Vera gate required before merge; Sam native-speaker pass is the merge gate.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Wren — UX Lead copy review of b1640fdc (zh-cn-ui-localization two-pass + adjudication)

Terminology pass: glossary holds everywhere it applies — 智能体, Pod in English with half-width spaces, 工作空间, 私信, 连接器, 频道; no 您, no 通过…进行/以…方式/带有 calques, CJK–Latin spacing correct in all 171 new strings. Voice pass: mostly native; the findings below are the exceptions. Adjudication: glossary wins on terms, voice on rhythm.

Blocking (the page still mixes languages)

The coverage invariant scans only t( calls, so it cannot see the English that is still hard-coded in the two components and interpolated into zh strings:

where what a zh-CN user sees
V2ConnectorTools.tsx:93 relativeTime just now / 2h ago / 3d from now / in a moment inside 已授权 {{rel}}, the trail's when column, and budgetWindow (whose .replace(' ago','') hack only works in English)
V2ConnectorsPage.tsx:169 second relativeTime same, English only
V2ConnectorsPage.tsx:565,578,594,612,655,713,714,778,960 template literals started ${…}, since ${…}, added ${…}, Slack answered ${…}, paused ${…}

Ask: one shared relativeTime(t, date) (or reuse the existing time.* keys at en.json:385, which already have _one/_other plurals) returning 刚刚 / {{count}} 分钟前 / {{count}} 小时前 / {{count}} 天前 / {{count}} 天后; the five literal prefixes become keys (connectors.sinceRel 自 {{rel}}… or restructure); budgetWindow gets its own window formatter (每 {{window}} 可调用 {{calls}} 次 with window = 1 小时 / 1 天), no string surgery. Extend the invariant so a template literal containing ${relativeTime( in these two files fails.

Once rel is 2 小时前, the zh word order must lead with it (the trailing-adverbial calque is a banned pattern in the skill):

key before after
tools.grantedWhen 已授权 {{rel}} {{rel}}授权
tools.revokedByLine 已由 {{member}} 撤销 {{rel}} {{rel}}由 {{member}} 撤销
tools.revokedLine 已撤销 {{rel}} {{rel}}撤销
tools.expiredLine 已过期 {{rel}} {{rel}}过期
tools.grantedByOn 由 {{member}} 授权于 {{rel}}。 {{rel}}由 {{member}} 授权。
tools.grantedOn 已授权 {{rel}}。 {{rel}}授权。
tools.endedRevokedBy 已由 {{member}} 撤销({{rel}})。 {{rel}}由 {{member}} 撤销。
tools.endedRevoked 已撤销({{rel}})。 {{rel}}撤销。
tools.endedExpired 已过期({{rel}})。 {{rel}}过期。
tools.endsRel 将于 {{rel}} 结束。 {{rel}}结束。 (rel = 3 天后)

Terminology corrections (rulings)

key before after reason
connectors.codeError 无法创建新代码。 无法生成新的启用码。 代码 = source code; the enable code is 启用码
connectors.newCode 新代码 新启用码 same
connectors.showCode 显示代码 显示启用码 same
connectors.codeExpired 启用代码已过期。 启用码已过期。 same
connectors.askOperator 联系你的管理员 联系实例管理员 operator ≠ administrator; the EN keeps them distinct and so must zh
connectors.providerNotConfigured 此实例尚未启用 {{connector}}。请联系你的管理员。 此实例尚未启用 {{connector}},请联系实例管理员。 same
connectors.pausedRefusal 管理员已暂停此连接器,请联系你的管理员。 管理员已暂停此连接器,请联系实例管理员。 removes the 管理员…管理员 loop
tools.notEnabled 此实例尚未启用 · 联系你的管理员 此实例尚未启用 · 联系实例管理员 same
connectors.ask, tools.ask 请求 申请 a bare 请求 is not a button; 申请 is the verb-first request-access button
connectors.activeTag 活跃 当前 active here = the pod this channel currently serves, not "lively"
connectors.makeActive 设为活跃 设为当前 same
connectors.rowRelayOff / mode / gateSwitch / relayOffHint 中继 转发 转发 is what Chinese chat products call relaying messages to a channel; 中继 is a network-hardware term. Apply to every 中继: 转发已关闭, 转发模式, 转发 {{pod}}, 你的转发、模式和 Pod 都会保留
connectors.rowAttention 仅提醒 · 升级消息会发送到频道 仅提醒 · 需要关注的消息才会发送到频道 升级 reads as "upgrade"; escalation = 需要关注的消息
connectors.attentionHint 只有升级消息和主导智能体的消息会发送到频道。 只有需要关注的消息和主导智能体的消息会发送到频道。 same

Voice (rhythm; take them)

key before after
connectors.description …它会在那里用自己的名字回复… …它会在那里以自己的名义回复…
connectors.gatesTitle 会将消息发送到此频道的 Pod 接入此频道的 Pod
connectors.pickWhere 选择消息发送位置 选择消息发往哪里
connectors.notLinkedDetail 你的消息没有发送目标 你的消息还没有去处
connectors.slackConfirmRow {{workspace}} 显示 {{user}} 已连接 — 是你吗? {{workspace}} 显示 {{user}} 已连接,是你吗?
connectors.leadAny 任意智能体均可主导 任一智能体都可主导
tools.asksNothing 无需先请求确认 无需确认
tools.asksEveryWrite 每次写入都需先请求确认 每次写入前都需确认
tools.asksList {{tools}} 需先请求确认 {{tools}} 调用前需确认
tools.asksFirst 需先请求人工确认 调用前需人工确认
tools.modeWriteConfirm 读取和写入,先请求确认 读取和写入,写入前需确认
tools.staleAfterChange …撤销没有完成。 …撤销未成功。

Out of scope for this PR (filed on the board)

One Telegram chat, one pod. / One Slack workspace, one pod. / GitHub issues and pull requests. in the capture are server data: backend/integrations/manifests.ts catalogue descriptions and the Installable description. A frontend PR cannot localise them; that is a manifest-level description map or a client override keyed by connector id — separate task.

Evidence: connectors-tools-zh-CN-1200.png is a real PNG (1200×1000), zh-CN rail active, page in Chinese except the server strings above. I look again at the next head.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Addendum to the change table (Sam 68723):

key before after reason
connectors.footnote …发送给机器人… keep This is the literal Telegram bot (@BotFather object), not an agent. The glossary bans 机器人 only as a rendering of "agent"; a Telegram bot is 机器人 in Telegram's own zh-CN UI. Defensible as written.

Row descriptions ("One Telegram chat, one pod." etc.) are Installable.description on the DB row, seeded from the manifest by backend/scripts/seed-builtin-connectors.ts, not frontend keys. Ruling in pod: per-locale description on the Installable row projected by the caller's locale (TASK-038), not a frontend override by installableId. This PR stays chrome-only, including Vera's class (2) literals per Sam 68733.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Second pass at exact head 1e61051a (Wren, zh-cn-ui-localization two-pass). The first table landed in full; the 1200 capture reads clean (2 小时前添加, 已连接到, 申请, 设为当前, 转发, 实例管理员). Remaining edits on the strings this head added:

key before after reason
connectors.sinceWhen {{rel}}更新 {{rel}}起 "since" marks the start of a state (gate on, dropped); 更新 asserts an update that may not have happened. 2 小时前起 / 刚刚起.
connectors.slackAnsweredWhen {{rel}} Slack 回复 Slack {{rel}}回复 Subject first, then time, in Chinese rhythm: Slack 2 分钟前回复.
connectors.waitingToConnect {{label}} 等待连接。 {{label}} 正在等待连接。 Progressive "is waiting"; bare 等待连接 reads as an instruction.
connectors.yourSlackUser 你的 Slack 用户 你的 Slack 账号 A person's own identity on a service is 账号; 用户 is someone else.
tools.time.inAMoment 一会儿后 即将 Composes with the rel-lead templates: 即将结束。/ 即将过期。 一会儿后结束 is a calque.
connectors.time.* + tools.time.* two identical nine-string families, two localizeRelativeTime copies, two relativeTime copies one time.* family (no time namespace exists on main), one exported localizeRelativeTime from V2ConnectorTools.tsx imported by the page; keep time.*Duration beside it Sam 68733 asked for one pattern for both files; every duplicate string is a line Sam's native pass reads twice and a place the two can drift.

Note for the gate (Vera's call, not blocking my look): the negative check in v2-i18n-invariants.test.ts is a list of the exact ten literals this head removed, so a new `removed ${…}` literal passes it. A generic shape would be /`[A-Za-z]+ [^`]*\$\{/ plus /\|\| '[A-Za-z][A-Za-z ]+'/ over source, with any known-safe hit named in the test.

The 管理员 wrap in the GitHub row's "联系实例管理员" (管理 / 员 across two lines) is CJK line breaking in a narrow column, not copy; out of scope here.

I look again at the next head; then Vera's gate and the hold for Sam's native pass.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Follow-up at head b356bf46c: applied the second-pass copy table: connectors.sinceWhen now {{rel}}起; Slack reply now Slack {{rel}}回复; waiting state now 正在等待连接; Slack identity now 你的 Slack 账号; future relative time now 即将. Both pages now use one shared top-level time.* family and exported localizeRelativeTime; connector floor/clamp behavior and Tools rounding/future behavior are preserved. Added unit coverage for missing/future timestamps. Full frontend: 110 suites, 880 tests; build passes. Awaiting Wren look, Vera gate, and Sam native-speaker pass.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Structural follow-up at head b9e46b6e: replaced English-output parsing with exported relativeParts(date, options), returning missing/now/rel semantic parts. relativeTime and localizeRelativeTime now format directly from those parts; no regex or dependency on English wording. Tests cover missing + connector future clamp, every minute/hour/day ago/from-now path, counted translation keys, and formatter parity. Full frontend: 110 suites, 885 tests; build passes.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Wren — two-pass copy review at exact head 2008e7aa (final table)

Scope read: all 215 changed/new keys under connectors.*, tools.*, time.* at this head (dump diffed against main). Voice pass + terminology pass per the zh-cn-ui-localization skill, adjudicated below.

Landed at this head (from the earlier two tables — nothing outstanding): 启用码, 实例管理员 vs 管理员, 申请, 当前/设为当前, 转发, 需要关注的消息, {{rel}}-leading time lines, {{rel}}起, Slack {{rel}}回复, {{label}} 正在等待连接。, 你的 Slack 账号, 即将, one time.* family, one shared helper, relativeParts (Sam 68750), localized missing (68759). 您 = 0. No 中继. Glossary terms (智能体 / Pod / 工作空间 / 私信 / GitHub App) consistent across every key. Capture byte-identical to 1e61051a.

Two rows for the native-pass fold (no new head needed before Sam reads):

key before after reason
tools.budgetWindow 每 {{window}} 可调用 {{calls}} 次 每 {{window}}可调用 {{calls}} 次 {{window}} renders through time.*Duration and always ends in CJK (分钟/小时/天); the half-width space rule is CJK↔Latin only, so today's output is 每 1 小时 可调用 5 次 with a stray space before 可.
connectors.description 每个频道从一个 Pod 接入 1 个智能体。… 每个频道从一个 Pod 接入一个智能体。… One sentence mixes 一个 and 1 个; use the character form for both (the numeral form is the marketing-count register).

Notes for Sam's ear (no change proposed):

  • {{rel}}起 with rel = 刚刚 renders 刚刚起 — only reachable when gate.since is absent; acceptable to me, flagging for the native read.
  • tools.days ({{count}} 天后) is the expiry <select> label, distinct from time.daysFromNow; keeping both is right.
  • time.* carries base + _one + _other triplets in zh-CN although zh-CN has only the other category — harmless, and the parity test wants identical key sets, so leave it.

Code follow-up, not blocking (Wren's call): localizeRelativeTime now branches on missing === 'just now' || missing === 'in a moment' to pick the key — an English-literal compare in miniature. Cleaner shape when this file is next touched: missingAsNow?: boolean (or missingKey) on RelativeTimeOptions, relativeParts returns { kind: 'now', future: false } for a missing date when set, and the page passes that instead of English text. Output unchanged.

Cleared for my part at 2008e7aa with the two rows above riding Sam's native pass. Vera re-gates the head; HOLD for Sam.

@lilyshen0722

lilyshen0722 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Change table addendum: zh-CN strings for the nine tools.* keys #1722 introduces (Sam 68891, Vera 68892)

Per Sam's ruling, #1722 lands first with en entries only; this PR then adds the zh-CN entries for exactly these keys in one push, and Wren + Vera re-clear that delta. Terms follow the existing table: admin = 管理员 (as in tools.noConnection), room = Pod (as in tools.toRoom), grant = 授权, GitHub App stays English with half-width spaces.

key en (from #1722) zh-CN reason
tools.installGitHubApp Install GitHub App 安装 GitHub App button, verb-first; matches 请先安装 GitHub App in noConnection
tools.adminSetup administrator setup 管理员设置 eyebrow label, noun phrase
tools.githubAppSetupHint Connect the GitHub App once so people can grant GitHub tools to their rooms. 只需连接一次 GitHub App,成员就能把 GitHub 工具授权给自己的 Pod。 once → 只需…一次; rooms → Pod; no 通过…进行… calque
tools.installationId installation ID 安装 ID field label; ID stays Latin, half-width space
tools.owner owner 所有者 GitHub's own zh UI term for the owner field
tools.repository repository 仓库 matches #1719's 仓库中的 issue 和 pull request
tools.installingGitHubApp Installing… 安装中… parallels granting = 授权中…
tools.githubAppRequired Enter the installation ID, owner, and repository. 请填写安装 ID、所有者和仓库。 enumeration with 、; 填写 for form fields
tools.githubAppError Could not install the GitHub App. 无法安装 GitHub App。 parallels grantError = 无法授权。

Sam's native pass sees these with the rest of the table.

Addendum (Wren, after #1722 head e758bda): #1722 also carries a bare English literal for the second-admin case; once keyed as tools.githubAppOwnedByOther (en entry in #1722), its zh-CN entry for #1717 is:

key en zh-CN
tools.githubAppOwnedByOther This GitHub App is already installed by another administrator. 这个 GitHub App 已由另一位管理员安装。

Ten keys total, not nine.

en column = the exact defaultValue strings in #1722 at 084f431; copy them verbatim into en.json.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant