Repository navigation
feat(i18n): localize Connectors and Tools in zh-CN - #1717
lilyshen0722 wants to merge 10 commits into
Conversation
Wren — UX Lead copy review of
|
| 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.
|
Addendum to the change table (Sam 68723):
Row descriptions ("One Telegram chat, one pod." etc.) are |
|
Second pass at exact head
Note for the gate (Vera's call, not blocking my look): the negative check in 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. |
|
Follow-up at head |
|
Structural follow-up at head |
Wren — two-pass copy review at exact head
|
| 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}}起withrel = 刚刚renders刚刚起— only reachable whengate.sinceis absent; acceptable to me, flagging for the native read.tools.days({{count}} 天后) is the expiry<select>label, distinct fromtime.daysFromNow; keeping both is right.time.*carries base +_one+_othertriplets in zh-CN although zh-CN has only theothercategory — 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.
2008e7a to
57fe417
Compare
Change table addendum: zh-CN strings for the nine
|
| 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.
57fe417 to
ef96cca
Compare
ef96cca to
2c54935
Compare
…ASK-164) Two zh strings Wren folded at #1717 that neither its head nor this branch carried: the budget window had a stray space before 可 (每 1小时 可调用), and the connectors description read "1 个智能体" where the ruling is 一个. Both are the UX gate's fold request, applied here rather than left as a follow-up because they are one-line catalog values on strings this PR already touches. Also :lang(zh) text-transform: none for the two mono labels that lowercase their Latin — .v2-connector-gates__title and .v2-tools__field's first child/legend. Their zh copy is CJK plus `Pod`, and CSS was rendering it `pod`, contradicting the sentence beside it. Case is not a property of CJK, so the reset is scoped to the label, not the type scale. Presence-guarded in v2-layout-invariants.test.ts, with the two underlying lowercase rules asserted first so the reset cannot pass by guarding a rule that no longer exists; control: renaming the selector reds it, restoring greens it.
ux-lead's post-gate note: the 结束时间 select offered 「7 天后」 beside 「3天后结束。」 in the same panel. tools.days was the ONE spaced value among this PR's 186 added zh keys (6 unspaced: the new time.age.* family), because it was lifted verbatim from #1717 rather than spelling the construction this PR ships. Same construction, one spelling. Not decided here and measured separately: on main the spaced form is the MAJORITY for number+unit (31 keys, e.g. inviteModal.options.days7 "7 天", podChat.catchup.hoursAgo) against 4 unspaced (this PR's three time.age.* keys plus yourTeam.card.age.days). So the new family is the minority style; that is lily-shen's call as a catalog-wide question, and this commit only stops this PR from shipping two spellings of one phrase.
…ASK-164) Two zh strings Wren folded at #1717 that neither its head nor this branch carried: the budget window had a stray space before 可 (每 1小时 可调用), and the connectors description read "1 个智能体" where the ruling is 一个. Both are the UX gate's fold request, applied here rather than left as a follow-up because they are one-line catalog values on strings this PR already touches. Also :lang(zh) text-transform: none for the two mono labels that lowercase their Latin — .v2-connector-gates__title and .v2-tools__field's first child/legend. Their zh copy is CJK plus `Pod`, and CSS was rendering it `pod`, contradicting the sentence beside it. Case is not a property of CJK, so the reset is scoped to the label, not the type scale. Presence-guarded in v2-layout-invariants.test.ts, with the two underlying lowercase rules asserted first so the reset cannot pass by guarding a rule that no longer exists; control: renaming the selector reds it, restoring greens it.
ux-lead's post-gate note: the 结束时间 select offered 「7 天后」 beside 「3天后结束。」 in the same panel. tools.days was the ONE spaced value among this PR's 186 added zh keys (6 unspaced: the new time.age.* family), because it was lifted verbatim from #1717 rather than spelling the construction this PR ships. Same construction, one spelling. Not decided here and measured separately: on main the spaced form is the MAJORITY for number+unit (31 keys, e.g. inviteModal.options.days7 "7 天", podChat.catchup.hoursAgo) against 4 unspaced (this PR's three time.age.* keys plus yourTeam.card.age.days). So the new family is the minority style; that is lily-shen's call as a catalog-wide question, and this commit only stops this PR from shipping two spellings of one phrase.
…eam-Commonly#1981) * fix(v2): route the Connectors and Tools copy through i18n (TASK-164) Both surfaces rendered English under zh-CN: 180 keys were missing from the i18n namespaces and neither component was in the eslint manifest, so translationKeys.test.ts never saw the literals they used. - en.json + zh-CN.json: connectors.* and tools.* keys, identical key sets - V2ConnectorsPage/V2ConnectorTools: hardcoded copy -> t(), including three line builders that interpolated around a literal (linkedLine, waitingToConnect, mayUse) so a reordering language can move the clause - joinList() for the list separator (、 in zh) rather than ', ' at each site - .eslintrc.js manifest: add both files so i18next/no-literal-string covers them * fix(v2): the Tools grant aside says pod, not room (TASK-164) lily-shen's zh read on Team-Commonly#1981: the product noun is pod and never a synonym, so tools.toRoom reads Pod in zh-CN and pod in en, matching the sibling tools.aPod in the same aside. * fix(v2): two new en strings still called a pod a room (TASK-164) Sam's ruling is that the product noun is pod and never a synonym. Both zh strings already said Pod; only the English source missed it. Found by a systematic pass over this PR's 178 added keys for the case 'zh says Pod but the en value never says pod', which returns exactly these two. * fix(v2): fill {{rel}} from the locale catalog, not an English literal (TASK-164) The zh render read "2h ago由 sam 授权。" / "每 1h 可调用 5 次" / "since 3h ago" / "3m ago": every {{rel}} was filled by a per-component English formatter, so the surrounding line was translated and the time was not. Both components now share one formatter (v2/utils/localizeRelativeTime) over the `time.age.*` keys the two surfaces already use for ages — one literal per unit and direction, so the manifest can enforce them, and `localizeWindow` for the budget window. The English grammar is unchanged (the two helpers were parameterized into includeFuture/missing/rounding, so the en strings byte-match what the tests already pinned), and the render's own `now` is threaded through so TASK-131's ticking clock still decides what a render shows. The gate line (`since ${relativeTime(...)}`) was the last raw English sentence on the page; it is `connectors.sinceWhen` now. * fix(v2): apply Wren's two folds, and stop lowercasing the Pod noun (TASK-164) Two zh strings Wren folded at Team-Commonly#1717 that neither its head nor this branch carried: the budget window had a stray space before 可 (每 1小时 可调用), and the connectors description read "1 个智能体" where the ruling is 一个. Both are the UX gate's fold request, applied here rather than left as a follow-up because they are one-line catalog values on strings this PR already touches. Also :lang(zh) text-transform: none for the two mono labels that lowercase their Latin — .v2-connector-gates__title and .v2-tools__field's first child/legend. Their zh copy is CJK plus `Pod`, and CSS was rendering it `pod`, contradicting the sentence beside it. Case is not a property of CJK, so the reset is scoped to the label, not the type scale. Presence-guarded in v2-layout-invariants.test.ts, with the two underlying lowercase rules asserted first so the reset cannot pass by guarding a rule that no longer exists; control: renaming the selector reds it, restoring greens it. * test(v2): pin localizeWindow's minute clamp (TASK-164) sprint-review's code gate found the one line in this PR with no test: dropping `Math.max(1, …)` in localizeWindow passed the whole suite (232 green) while changing what a sub-minute grant window renders. Covered at the unit rather than through a component whose fixture happens to use a one-hour window. The killing case is not the one proposed: 30000ms is exactly 0.5 minutes and `Math.round(0.5) === 1`, so it survives the mutation. 1ms is the case that does not — measured, not argued: clamp removed → `0m` / `0分钟`, and this file is the only red (1 failed / 2 passed). * zh-CN: drop the stray space in tools.days (TASK-164) ux-lead's post-gate note: the 结束时间 select offered 「7 天后」 beside 「3天后结束。」 in the same panel. tools.days was the ONE spaced value among this PR's 186 added zh keys (6 unspaced: the new time.age.* family), because it was lifted verbatim from Team-Commonly#1717 rather than spelling the construction this PR ships. Same construction, one spelling. Not decided here and measured separately: on main the spaced form is the MAJORITY for number+unit (31 keys, e.g. inviteModal.options.days7 "7 天", podChat.catchup.hoursAgo) against 4 unspaced (this PR's three time.age.* keys plus yourTeam.card.age.days). So the new family is the minority style; that is lily-shen's call as a catalog-wide question, and this commit only stops this PR from shipping two spellings of one phrase.
Summary
connectors.*andtools.*key used by the v2 pagesVerification
npx jest --watchAll=false --runInBand src/v2/__tests__/v2-i18n-invariants.test.tsnpx jest --watchAll=false --runInBand src/v2/__tests__/V2ConnectorsPage.test.tsx src/v2/__tests__/V2ConnectorTools.test.tsxnpm run buildUX Lead review and Vera gate required before merge; Sam native-speaker pass is the merge gate.