Skip to content

fix(i18n,app-shell): 补齐 8 处无兜底裸 key 调用点的十语言包文案 (#3546 切片一) - #3583

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3546-raw-key-sites
Aug 7, 2026
Merged

fix(i18n,app-shell): 补齐 8 处无兜底裸 key 调用点的十语言包文案 (#3546 切片一)#3583
yinlianghui merged 1 commit into
mainfrom
claude/issue-3546-raw-key-sites

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes-part-of #3546

#3546 切片一。#3547 的守卫量出 258 个 t() 引用但十个包都没有的 key,本切片只处理其中没有可用内联兜底的 8 处调用点(工单正文标出的那一批),背后是 5 个 key。其余 250 个按命名空间的批次串行开,本 PR 不碰。

⛔ 没有新增任何 defaultValue —— 那正是让 258 个 key 藏了几个月的机制,工单明令禁止。

⚠️ 前置核实推翻了工单对其中 2 处的定性

按"工单是线索不是规格"的要求,动手前在 origin/main 上逐点实测了这 8 处挂载 provider 后的真实渲染值。结论是 8 处里只有 6 处真的把 raw key 给了用户,另外 2 处的兜底本来就是生效的:

调用点绑定修复前实测(挂 provider)工单说法判定
ObjectView.tsx ×5useObjectTranslation() 裸用"console.objectView.cannotEditMetaView"raw key✅ 成立
RecordActivityTimeline.tsxcreateSafeTranslation(…, 'detail.back')"detail.viewSource"raw key✅ 成立
WizardForm.tsxcreateSafeTranslation(…, 'wizard.missingRequired')"Please complete the required fields: A, B"raw key不成立
GanttView.tsxuseGanttTranslation()"Refresh"raw key,读屏用户听到 key不成立

两处不成立的原因各不相同,都在代码里写着:

  • WizardFormtestKey 就是缺失的那个 key 本身。createSafeTranslation 的探针因此正确地判定"翻译未配置",交出 fallbackT,英文默认值照常渲染。
  • GanttView 走的 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 / cannotDeleteMetaViewdetail.viewSourcegantt.toolbar.refreshwizard.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 条,missingKeys 258 → 253,missingPrefixes 4 条未动(那 4 个整族缺失前缀属于后续切片)。

验证(方向都是跑之前先预测的)

守卫node scripts/check-i18n-call-site-keys.mjs:

指标预测实测
退出码00
可解析调用点2043 → 2051(+8)2043 → 2051
en 包 key 数2641 → 2646(+5)2641 → 2646
基线 missingKeys258 → 253(−5)258 → 253

逆向验证 A —— 中间态(只补 en,九包回退),两个预测都精确命中:

  • 平价测试对九个语言各报 is missing 5 key(s),列出的正是本切片这 5 个;
  • 棘轮同时转红,理由是"条目已修好却还留着":
    5 baseline entries are stale — the defect is gone, so the entry must go too
    missingKeys: console.objectView.cannotDeleteMetaView
    ...
    EXIT=1
    
    这正是棘轮该有的行为 —— 只补 en 不补包、或补完不删条目,都拦得住。

逆向验证 B —— 新增的 zh 断言真的有鉴别力。 从 zh 包单独摘掉 gantt.toolbar.refresh 后重跑新测:

× is labelled in Chinese under a zh provider (the pre-fix failure)
AssertionError: expected 'Refresh' to be '刷新'

红的方式本身就是上面那张表的直接证据:修复前 zh 会话拿到的是英文默认值,不是 raw key。同一文件的 en 用例保持绿 —— 这也说明光靠 en 断言等于没断言(那两处修复前后的 en 输出完全相同),真正钉住修复的是 zh 那半边。

测试(仓库根 vitest)

packages/i18n 23 files / 284 tests ✅
packages/plugin-gantt + plugin-detail + plugin-form 124 files / 1124 tests ✅
packages/app-shell 285 files / 2492 passed, 1 skipped ✅

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 包漂移;并显式钉住"探针 key detail.back 仍可解析"这个前提,否则上面两条会以错误的理由变绿。

顺带发现,未在本 PR 修改

已另立 #3582(未认领、未打标签,交分诊):console.objectViewsystemViewReadonlyexpandToPage 两个 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

#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
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 7, 2026 2:34pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-OcfiMNC3.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)480.72KB105.64KB
core (index.js)2.96KB1.13KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)137.51KB35.11KB
fields (index.js)230.87KB56.83KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.05KB1.53KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)112.03KB28.88KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)232.79KB57.42KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)112.10KB27.10KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)186.61KB49.34KB
plugin-kanban (index.js)48.30KB13.28KB
plugin-list (index.js)105.12KB25.48KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.46KB1.21KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 7, 2026 14:48
@yinlianghui
yinlianghui added this pull request to the merge queueAug 7, 2026
Merged via the queue into main with commit 6d762daAug 7, 2026
19 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3546-raw-key-sites branch August 7, 2026 14:48
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yinlianghui@claude