Uh oh!
There was an error while loading. Please reload this page.
fix(i18n,app-shell): 补齐 8 处无兜底裸 key 调用点的十语言包文案 (#3546 切片一) - #3583
Merged
Conversation
#3546 的守卫量出 258 个 t() 引用但十个包都没有的 key,其中 8 处调用点没有 可用的内联兜底。本切片补齐这 8 处背后的 5 个 key(en + 九包实译),并删掉 ObjectView 里五处永不触发的 `|| '英文'` 死兜底,同步缩减棘轮基线。 前置核实推翻了工单对其中 2 处的定性(见 PR 正文):只有 ObjectView 五处和 RecordActivityTimeline 一处真的把 raw key 渲染给用户;WizardForm 与 GanttView 的兜底本来就生效,渲染的是英文而非 key。修法不变,严重度分级变了。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 7, 2026 14:48
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 7, 2026
Closed
fix(i18n): 八包补上 console.objectView 两个 key 的真译,systemViewReadonly 按 en 当前的 read-only 语义 (#3582)
#3627
Merged
Closed
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 10, 2026
… 当前的 read-only 语义 (objectstack-ai#3582) (objectstack-ai#3627) `console.objectView.systemViewReadonly` / `expandToPage` 在 ja/ko/de/fr/es/pt/ ru/ar 八个包里存的是英文;`systemViewReadonly` 存的还是 en 自己已经废弃的旧话 (`- duplicate to customize.`),把八种语言的用户指向一条产品已不提供的操作路径。 八包按 en 当前语义重译,en / zh 一字节不动,不增删任何 key。 译文取自各包自己的既有措辞而非自创:`view.readonlyTooltip` 供「系统视图」、 `console.objectView.cannotEditMetaView`(objectstack-ai#3583 落的)供「由代码定义」; `expandToPage` 对齐同包 `detail.openAsFullPage` —— 那是隔壁命名空间里 一模一样的英文句子,十包早已译好,en/zh 两个 key 本就逐字节相同。 新增 objectView-value-language-3582.test.ts:钉住 en 字面量(下次改 en 文案 即红,逼九个译文包同批跟改)、断言除 en 外无包等于新旧任一句英文、要求 zh/ja/ko/ru/ar 的值含本文字系统字符。all-locales-key-parity 修前修后都绿 —— 它比的是键集合与占位符形状,这正是它看不见本缺陷的原因。 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 10, 2026
…jectstack-ai#3814) `scripts/check-i18n-call-site-keys.mjs`(objectstack-ai#3530)实测:`auth` 26 + `oauth` 16 + `acceptInvitation` 12 = 54 个 key 被 `t()` 引用而**任何语言包 都没有**,54 个不重复 key 对应 54 个调用点(本切片恰好 1:1;切片二是 90 key 93 站点,分母一律脚本实测,不手数)。54 处全部写了内联 `t(key, { defaultValue: '英文' })`,即 objectstack-ai#3517 那一类:英文照常渲染,十种语言 统统翻不了。裸 key 站点在切片一(PR objectstack-ai#3583)已清完;本切片对三个命名空间的 全部 122 个调用点做了 AST 扫描,死兜底 `t(key) || '英文'` 为 0 —— 所以不动 任何组件文件。 - `en.ts` 补 54 key:`oauth.consent.*` 与 `acceptInvitation.*` 是全新顶层 命名空间,其余 26 个扩充 `auth.login` / `auth.forgotPassword` / `auth.device` / `auth.verifyEmail`。52 个字面量 defaultValue 站点的 en 值 与调用点逐字节相同(脚本比对 52/52),包路径与内联兜底不可能分叉;剩下 2 个 (`oauth.consent.title` / `.request`)的 defaultValue 是 JS 模板,字节相同 在结构上不可能(`${…}` vs `{{…}}`),按调用点实际声明的插值契约落值。 - 九包实译按同包邻键取证(fr 冒号/问号前排版空格、de 半角破折号、ru ё、 ar 动词居首避免 RTL 句首落 Latin token、zh 全角标点)。十包唯一共享的 字符串是 `phonePlaceholder`(E.164 示例号,与包内既有 `name@example.com` 同类),以精确集合钉死 18 对。 - 棘轮 `scripts/i18n-call-site-key-baseline.json` 精确减 54(163 → 109)。 - `{seconds}` 是 auth 表单自己的 `.replace()` 洞而非 i18next 插值 (`LoginForm.tsx:429`/`ForgotPasswordForm.tsx:367`),十包都必须保留单花括号, 测试双向钉扎。 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 10, 2026
…objectstack-ai#3546 切片四) (objectstack-ai#3839) `scripts/check-i18n-call-site-keys.mjs`(objectstack-ai#3530)实测:`console` 命名空间有 41 个 key 被 `t()` 引用而**任何语言包都没有**,对应 **47 个调用点**(五个 key 多站点, `console.ai.dock.maximize` 有三个;分母一律脚本实测,不手数)。47 处全部写了内联 `t(key, { defaultValue: '英文' })`,即 objectstack-ai#3517 那一类:英文照常渲染,十种语言统统 翻不了。裸 key 站点在切片一(PR objectstack-ai#3583)已清完;对全仓 308 个 `console.*` 调用点 做 AST 扫描,本切片 key 的死兜底为 0 —— 所以不动任何组件文件。 - `en.ts` 补 46 key = 实测的 41 + 前缀家族 5。`console.notFound` 是新子命名空间, 其余扩充 `console.shortcuts` 与 `console.ai`(新增 `dock` / `designingPlanHint` / `group` 三个子对象)。41 个 key 的 en 值取调用点内联 defaultValue **逐字节相同**(脚本比对 46/47 站点)。 - 唯一不能相同的一处:`ChatDock.tsx:562/563` 用**同一个 key** (`console.ai.dock.open`)写了两句英文 —— `aria-label` 是 `Open assistant`, `title` 是 `Open assistant (⌘⇧I)`。一个 key 只能有一个值,en 取 aria-label 那句:无障碍名不能带被读屏念成符号的字形,且 `⌘` 是 mac 专有字形,语言包无法 按平台分叉。后果(title 不再提示该快捷键)记在 PR 正文并回填进 objectstack-ai#3810。 - `console.ai.group.` 从棘轮 `missingPrefixes` 摘除(4 → 3)。它是模板 key (`ConversationsSidebar.tsx:277`),取值面是**封闭**的 `ConversationGroupKey` 联合,所以按枚举补齐五个成员而不是通配;测试读组件自己的联合与 fallback label 表,第六个分组一旦加入就红。 - 九包实译按同包 `console` 邻键取证(zh 全角标点与单破折号状态行、ja/ko 的 `AI ` 空格、de 敬语 Sie 与 `Wird …` 进行式、fr 直角撇号、es usted、pt `off-line` 拼法、ru ё、ar 动词居首不让 RTL 句首落 Latin token)。en 相同的字符串一律复用 邻键既有译文(`Go back` / `Publish failed` / `Try again` / `Back to home` / `Assistant` / `Today` / `Yesterday`)。 - 棘轮 `scripts/i18n-call-site-key-baseline.json` 精确减 42(41 key + 1 前缀): 109 → 68 key,4 → 3 前缀。切片二/三把总数硬钉在 109,两处同步改为 68。 - `zh` 的 `console.ai.planBuilding` 与 `AiChatPage.tsx:2195` 硬编码的 `convZh ? '正在搭建…'` 逐字节相同,并有断言读源码钉住两者 —— 那个三元本身是 独立缺陷(标签跟了会话语言而非 UI 语言),已立 objectstack-ai#3837,不在本 PR 修。 Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 10, 2026
…disclosure.runtime. 前缀家族,十包补齐 (objectstack-ai#3546 切片五) (objectstack-ai#3847) * fix(i18n): 回填 marketplace + preview 两命名空间 37 个缺失语言 key 及 marketplace.disclosure.runtime. 前缀家族,十包补齐 (objectstack-ai#3546 切片五) `scripts/check-i18n-call-site-keys.mjs`(objectstack-ai#3530 的守卫)实测:37 个不重复 key 在 37 个调用点上被 `t()` 引用而十个语言包一个都没有,外加 1 个整族缺失前缀 (`marketplace.disclosure.runtime.`,静态头在 en 一条都不匹配,所以插值取任何 值都是缺的)。分母以脚本实测为准不手数 —— 本切片罕见地是 1:1(前四切片都有 一 key 多站点,切片二预测 90 实为 93)。 37 处全部带内联 `defaultValue`,无一渲染裸 key(那类由切片一 PR objectstack-ai#3583 收), 所以严重度是 objectstack-ai#3517 类:英文照常渲染,十种语言全部卡在英文上。zh 会话下实际 看到的英文:应用市场「Your organization」区块与其 Install / Installing… / Installed 按钮、版本升级提示;ADR-0025 PD4 的**安装前权限披露**整块 —— This package contains code、信任层级徽章、Reviewed & approved / Not yet reviewed / Signed、四个权限组标签(平台服务/生命周期钩子/网络/文件系统)与用户勾选的 同意复选框;ADR-0045 未发布应用横幅及其发布 toast;ADR-0067 构建历史面板 整套 —— 标题、说明、每条提交的标签、Revert 按钮与它的两个结果 toast。 前缀家族按**封闭枚举**处理,不通配:取值面是 spec 的 `PluginRuntimeSchema` = `z.enum(['node', 'sandbox', 'worker'])`(ADR-0025 §3.6),故枚举三个成员, 该家族离开棘轮 `missingPrefixes`(3 → 2)。测试读组件自己的 `RUNTIME_FALLBACK` 兜底表(spec 在姐妹仓,本仓测试读不到),第四个层级加进来就红 —— 那正是棘轮 里那条 prefix 条目原本的职责,从棘轮搬进测试。 en 值与被替换的内联 `defaultValue` 逐字节相同(36 个字面量站点 36/36); 第 37 个站点的 `defaultValue` 是模板串 `` `Installed ${pkg.display_name}` ``, 逐字节比对结构上不可能,取其静态头加 i18next 的洞 `Installed {{name}}` —— `name` 参数该站点本来就在传。所以用户今天看到的英文一字未改。 九包实译逐包取邻键证据,en 字符串已存在的一律复用邻键既有译文而不另造 (10 对,含 form.update / marketplace.installedBadge / preview.draftBar.publish / home.pendingDrafts.publishFailed / detail.history),使一句英文不会在同一 语言里渲染成两句。两个命名空间在 zh 里合法地取**不同**第二人称 (marketplace 你 3:0,preview 您 2:0),es 的 marketplace 邻域一致 usted 9:0。 零组件改动:对 marketplace/preview 全部 172 个调用点(11 文件)做 AST 扫描, 量出 16 处 `t(key) || '英文'` 死兜底,10 个 key 在 en 全都有值、且一处也不属 本切片的 37 个 key,故一处未动(已回填 objectstack-ai#3810)。 棘轮按行删除而非重序列化:38 deletions,零插入。 顺手量出、未在本 PR 修:objectstack-ai#3844(es preview.draftBar 同一横幅两种敬称)、 objectstack-ai#3845(marketplace.action.updateTo 传了 version 但值里没有洞,全仓 1/1055)、 objectstack-ai#3846(marketplaceApi 把 runtime 声明为开放 string 而 spec 是封闭枚举)。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt * fix(i18n): ja 的 grantsIntro 尾冒号改半角,随该包「谓语结尾用半角、名词标签用全角」的既有条件式约定 自查 ja 包尾冒号分布时发现本切片自己写错了一处:`marketplace.disclosure.grantsIntro` 以谓语 `付与されます` 结尾,却给了全角 `:`。 ja 包在这件事上不是不一致,而是**有条件**的: - **谓语结尾 → 半角 `:`**,12 例中 10 例(五个 connectAgent 正文、 `oauth.consent.willAllow`、`auth.login.devAdminHint.body`、 `grid.bulk.affectedRecords`、`connectAgent.apiKey.showOnce`、 `preview.changes.loadFailed`); - **名词标签结尾 → 全角 `:`**,5 例中 4 例(`grid.import.mappingTemplate`、 `grid.import.savedMapping`、`auth.verifyEmail.sentTo`、 `preview.changes.detailChangedKeys`)。 本切片两个 ja 冒号都是谓语结尾,故都取半角。`preview.history.loadFailed` 本来就对(它另外还与同名兄弟键 `preview.changes.loadFailed` 逐字节相同); `grantsIntro` 没有同名兄弟,只能靠这条分布裁,之前裁反了。 同时把这条约定连同 zh(全包全角标点,13:6)与 ko(全包 16 处半角、0 处全角) 的反向对照写成断言,免得下一个读者以为 ja/zh 在同一个字串上取不同冒号是笔误, 也免得同一个错再犯一次。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt * fix(i18n): preview.history.items 九包改用 common.itemCount 的既有单位词,不再自造括号复数记法 `CommitTimeline.tsx:142` 渲染的是 `{c.itemCount} {t('preview.history.items')}` —— 数字由组件给,包只供单位词。这与 `common.itemCount`(en `{{count}} items`) 是**同一种「计数 + 单位」相邻结构**,而后者本仓十包早已译好。之前自造了括号 复数记法,两处错: 1. **ru 包全 2832 个值里一次也没用过 `(s)` 记法** —— 它要么改写句式 (`marketplace.detail.purgeSuccess` = `Удалено … записей: {{count}}.`), 要么缩写(`fields.relativeDate.overdue` = `Просрочено на {{count}} дн.`)。 `элемент(ов)` 是凭空引进的形状。 2. **`item(ns)` 在 pt 自己的追加式约定下根本得不到 `itens`**。该包的记法是 `linha(s)` / `registro(s)` / `coluna(s)` 这种词干加后缀,变干的情形写成 `alteração(ões)`(`home.pendingDrafts.message`)—— 无论按哪种读法, `item(ns)` 都不成立。 改为直接取 `common.itemCount` 去掉 `{{count}} ` 前缀后的单位词: zh 项 ja 件 de Elemente fr éléments es elementos pt itens ru элементов ar عناصر **ko 是唯一刻意的偏离**:`common.itemCount` ko 是 `{{count}}개 항목`,量词 개 紧贴数词不带空格;而本调用点在数字与单位之间强制插一个空格,照搬会渲染成 `3 개 항목`(空格落在数词—量词之间)。故舍量词留名词,渲染为 `3 항목`。 **没找到这个邻键的原因值得记下来**:本切片的复用清单是按 **en 逐字节相同** 搜邻键的,而 `common.itemCount` 的 en 是 `{{count}} items` 而不是 `item(s)`, 所以"同一概念、不同英文写法"的邻键对那个搜索是不可见的。已把这条连同上面的 裁定写成断言(并双向验证 `common.itemCount` 确实就是那个单位词),避免复发。 en 值不变(仍是调用点的 `item(s)`,逐字节纪律不动),故 en-drift、 en↔defaultValue 的 36/36、棘轮与前缀家族处理一概不受影响。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes-part-of #3546
#3546 切片一。#3547 的守卫量出 258 个
t()引用但十个包都没有的 key,本切片只处理其中没有可用内联兜底的 8 处调用点(工单正文标出的那一批),背后是 5 个 key。其余 250 个按命名空间的批次串行开,本 PR 不碰。⛔ 没有新增任何
defaultValue—— 那正是让 258 个 key 藏了几个月的机制,工单明令禁止。按"工单是线索不是规格"的要求,动手前在
origin/main上逐点实测了这 8 处挂载 provider 后的真实渲染值。结论是 8 处里只有 6 处真的把 raw key 给了用户,另外 2 处的兜底本来就是生效的:ObjectView.tsx×5useObjectTranslation()裸用"console.objectView.cannotEditMetaView"RecordActivityTimeline.tsxcreateSafeTranslation(…, 'detail.back')"detail.viewSource"WizardForm.tsxcreateSafeTranslation(…, 'wizard.missingRequired')"Please complete the required fields: A, B"GanttView.tsxuseGanttTranslation()"Refresh"两处不成立的原因各不相同,都在代码里写着:
testKey就是缺失的那个 key 本身。createSafeTranslation的探针因此正确地判定"翻译未配置",交出fallbackT,英文默认值照常渲染。useGanttTranslation刻意不用createSafeTranslation—— 它逐 key 兜底,源码注释明写理由(宿主字典常常译了通用 key、落下新 key,单 key 探针处理不了半译字典)。所以 host 未命中时落到GANTT_DEFAULT_TRANSLATIONS,aria-label是英文Refresh,不是 key。工单把createSafeTranslation的语义安在了一个明确规避它的钩子上。这 2 处仍然是缺陷,只是属于较轻的 #3517 类(英文照常渲染,十种语言译不了),不是"用户今天看得见裸 key"。分诊评论把 aria-label 那处当作"反对自己定级的最强论据",而它恰恰是被推翻的两处之一 —— 定级请据此重估。修法不变(5 个 key 一样要补),变的只是严重度。
改了什么
1. 五个 key 回填十包(
console.objectView.cannotEditMetaView/cannotDeleteMetaView、detail.viewSource、gantt.toolbar.refresh、wizard.missingRequired)。九包逐条按邻键的语气/词汇/标点惯例写,不是照抄:fr 用法语冒号前空格(
obligatoires : {{fields}},与邻键missingRequiredHint一致)、de 用该包惯用的–、es 用冒号代破折号(该包filterOrNotSavable就是这么写的)、pt 沿用exibição(与deleteViewTitle一致)、ko 用보기、ru 保留 ё 正字法(определённое)、ar 占位符置尾、zh 全角标点且中英文间不加空格。detail.viewSource/gantt.toolbar.refresh/wizard.missingRequired的英文与各自组件 defaults map 里的字符串逐字一致,避免 provider 路径与无 provider 路径分叉。2. 删掉 ObjectView 五处死兜底。 这五个
|| '英文'在 key 缺失时也永远不触发 —— i18next 未命中返回 key 本身,是非空字符串,||不会落下去。它们不是"兜底失效",是从写下那天起就不可达的代码。一个额外事实值得记录:这一个 key 服务四个调用点(重命名 / 置顶 / 设为默认 / 配置),四处死兜底写的是四句不同的英文。既然它们从未渲染过,本 PR 把包文案写成覆盖"对内置视图的任何修改"的一句,而不是挑其中一句。若维护者希望四种操作各有文案,那是拆 key 的独立改动。
3. 棘轮基线精确减 5 条,
missingKeys258 → 253,missingPrefixes4 条未动(那 4 个整族缺失前缀属于后续切片)。验证(方向都是跑之前先预测的)
守卫
node scripts/check-i18n-call-site-keys.mjs:missingKeys逆向验证 A —— 中间态(只补 en,九包回退),两个预测都精确命中:
is missing 5 key(s),列出的正是本切片这 5 个;逆向验证 B —— 新增的 zh 断言真的有鉴别力。 从 zh 包单独摘掉
gantt.toolbar.refresh后重跑新测:红的方式本身就是上面那张表的直接证据:修复前 zh 会话拿到的是英文默认值,不是 raw key。同一文件的 en 用例保持绿 —— 这也说明光靠 en 断言等于没断言(那两处修复前后的 en 输出完全相同),真正钉住修复的是 zh 那半边。
测试(仓库根 vitest)
turbo run type-check(i18n / app-shell / plugin-detail / plugin-gantt / plugin-form):33 successful, 33 total。pnpm check:control-bytes✅、pnpm changeset:check✅。新增测试为什么都挂 provider
工单记录的陷阱就是这个:不挂 provider 时 defaults map 会把这几个 key 全部答对,断言全绿而控制台是坏的。三个新文件都在
I18nProvider下跑,并且都用各调用点真实的绑定形态:packages/i18n/src/__tests__/raw-key-call-sites-3546.test.tsx—— 5 个 key × 十包完整性 + en/zh 经三种真实绑定解析;含一条"九包不得与 en 逐字相同"的断言(防止回填时把英文复制九份:那样键平价全绿而九种语言仍是英文,现有门禁看不见)。ObjectView 的绑定就是裸useObjectTranslation,中间没有任何一层,所以在绑定层断言是忠实的 —— 不必为读一句 toast 文案去渲染整个 ObjectView。packages/plugin-gantt/src/GanttView.refreshAriaLabel.i18n.test.tsx—— DOM 级,真渲染GanttView,直接读刷新按钮的aria-label(该按钮只有图标,aria-label就是它的全部可访问名)。packages/plugin-detail/src/__tests__/useDetailTranslation.viewSource.i18n.test.tsx—— 用真实的useDetailTranslation与真实 defaults map,顺带能发现 map 与 en 包漂移;并显式钉住"探针 keydetail.back仍可解析"这个前提,否则上面两条会以错误的理由变绿。顺带发现,未在本 PR 修改
已另立 #3582(未认领、未打标签,交分诊):
console.objectView的systemViewReadonly与expandToPage两个 key 在八个包里存的是英文原文,且前者存的还是一句en自己已经不用的过期文案(包里是 "duplicate to customize",en 现在是 "read-only")。键平价、#3547 守卫、棘轮三道全绿 —— 因为没有任何一道断言"包里的值是不是该语言"。与 #3546 的 258 个不同类(那是十包都没有 key,这是十包都有 key 但值是英文),故单独立单。文件面
严格限定在切片一:4 个组件文件里只动了那 8 个调用点及其死兜底(实际只有 ObjectView 需要改代码,另外 3 处无死兜底构造)、十个语言包只加本切片的 5 个 key、基线只删 5 条。其余 250 个 key 的条目一条未动。已核实与 #3518 / #3548 零文件相交(两者均已合并且早于本分支起点)。
Generated by Claude Code