fix(components,fields,plugin-detail,i18n): console chrome i18n gaps beyond #5084 (objectstack#5407) - #3379
Merged
Merged
Conversation
…objectstack#5407) - lookup gate hint names the controlling field by LABEL, not API name - page-header overflow trigger reads detail.moreActions - reaction button reads detail.addReaction (new key, 10 packs) - formInvalid joins with a per-locale validation.formInvalidJoiner - es validation.required/unique carry their own head noun (gender agreement) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
…chrome i18n fixes Also strips `dependsOnLabels` in `stripRendererOnlyProps`: the builtin field branch spreads leftover props straight onto its control, so every plain input logged "React does not recognize the `dependsOnLabels` prop on a DOM element" until the strip covered it. Pinned by the new builtin-branch test. 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
|
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 free
to 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 objectstack-ai/objectstack#5407
issue 列了四项。逐项复核
origin/main后:第 1、2(部分)、4 项成立并已修;第 2 项的Clear/Close复现不出来;第 3 项的归因不成立(是 app 元数据,不是 console chrome)。下面逐项给测量依据。1. 依赖门控的 lookup 占位符插的是 API 名 — 成立,已修
issue 猜"插的是 object 的 API 名,应改成 object label"。实测是受控字段(sibling field)的 API 名:
packages/fields/src/widgets/LookupField.tsx三处(trigger 正文、triggertitle、browse-all 按钮title)都写着depends_on存的就是 API 名,所以句子被翻译、被插进去的名字没有 —— 十种语言一律如此,en也一样,英文用户看到的是内部标识符。对照组说明这不是设计:
packages/components/src/renderers/form/form.tsx的固定选项字段(select / radio / multiselect / checkboxes)走的是同一个 key,但它把名字解析成了 label(fieldLabelByName[fn] || fn),而且源码注释里就写着 widget 那边插的是 raw metadata names。也就是说仓库早就知道两边不一致,只是没有把 label 递过去。修法(contract-first,不在消费端兜底): 只有 form 知道 label,所以由 form 递 —— 新增一个 widget prop
dependsOnLabels(field name 到 label 的映射),就是emptyHint在 lookup 侧的对应物。widget 读dependsOnLabels?.[d.field] || d.field,映射里没有的名字回落到自身 —— 没有宿主的独立 widget 渲染结果与改前逐字节相同。顺带修掉一个自己引入的问题:builtin 分支(
input/textarea/…)不经过stripRegisteredFieldProps,会把剩余 props 直接摊到控件上,所以新 prop 必须同时进stripRendererOnlyProps,否则每个普通文本框都会打印 "React does not recognize thedependsOnLabelsprop on a DOM element"。这条已由测试钉住。2. Toolbar / list chrome 无翻译 key — 两条成立已修,两条复现不出
More actions— 成立。containers.tsx的 page header 溢出触发器写死aria-label="More actions"。它是纯图标按钮,这个字面量就是它对读屏器/悬浮提示的全部名字。改读detail.moreActions—— 刻意复用action:menu自己那个溢出触发器已经在用的同一个 key,不新开一个:一张记录页可能同时出现两个⋯,两个 key 会让它们在某个语言下读法不一致。该 key 十包齐全,零新增词条。Add reaction— 成立。ReactionPicker.tsx写死aria-label="Add reaction"。新增detail.addReaction,十个语言包全部补齐(all-locales-key-parity强制全量对齐)。Clear— 复现不出。 console chrome 里每一处Clear都已经过t(),且十包均已翻译:list.clear(ViewSettingsPopover)、grid.bulkClear(BulkActionBar)、lookup.clear、approvalsInbox.clear、filterBuilder.*。唯一没走t()的是plugin-gantt的 QuickFilterBar —— 而它在所有语言下渲染的是中文"清除筛选",与 issue 描述的"stay English in every locale"正好相反。那是另一个 bug,已另开 objectstack#5427。Close— 定位不到。 issue 说是"visible button"。仓库里带可见文字的Close只有app-shell/.../external/ImportObjectDialog.tsx:192(Setup 的外部对象导入对话框);其余(navigation-overlay.tsx的aria-label/title、plugin-designer/DashboardEditor.tsx)都是图标按钮,而components/src/ui/dialog.tsx那个是 sr-only —— 属于 #5084 的清单,本单明确排除。没有截图无法判定验收扫到的是哪一个,不硬猜。剩余的图标按钮 label 已归入 objectstack#5430。3. Dashboard filter chips / KPI 副标题 — 归因不成立,本 PR 不改
issue 说这四项"unreachable from app metadata; every one needs a console bundle key"。第 3 项不是这样:
Owner:/Lead Source:前缀:DashboardFilterBar.tsx:226是def.label || def.name,def是 dashboard 元数据里的 filter 定义(DashboardFilterDef.label,packages/core/src/utils/dashboard-filters.ts:37)。冒号后面那个会翻译的值(全部 / すべて)才是 chrome(dashboard.filters.all)—— 这恰好解释了 issue 观察到的"值翻了、前缀没翻"。console 不可能用一个 bundle key 去翻一个它无从得知的 app 字段标签。Total Amount / Accounts / Contacts / Leads:DatasetWidget.tsx:404是headerLabel(values[0]),即语义层 dataset measure 的 label,而且它已经过useSafeFieldLabel().fieldLabel(object, name, fallback)的约定式解析 —— 只是 measure 不是对象字段,约定 key 命不中,于是落到 dataset 里作者写的英文 label。同样是 app 元数据。测量过程中确实翻出了 plugin-dashboard 自己的渲染缺陷(私有
resolveLabel接受I18nLabel却从不调t();type: 'metric'的 self-contained 分支整条绕过tWidgetTitle/tWidgetDescription),但它牵涉一个需要维护者定夺的设计选择,已单独开 objectstack#5428,不塞进本 PR。4.
en校验 toast 用顿号连接字段名 — 成立,已修form.tsx的announceFieldErrors里写死join('、')(U+3001)。zh/ja 碰巧对,其余全错,英文最扎眼。取舍(PM 给了两个方向,这里选前者,附实测理由):
validation.formInvalidJoiner。 拉丁语系 + 韩语", ",zh/ja"、",阿拉伯语 U+060C + 空格。新语言缺词条时经 i18next 回落到en的", ";all-locales-key-parity保证不漏包。Intl.ListFormat。 零词条听起来更优,但实测输出不能用 ——type: 'unit'在 zh 和 ru 下分隔符是空串(比现状更糟),type: 'conjunction'会插入 "and"/"和"/"y",而这是一个会被截断成 "A, B, C…" 的列表,插连词读起来是句子不是列表。而且它的输出取决于运行时 ICU 数据,词条则是确定且可 review 的。同层顺带修了 issue 点名的 es-ES 性数一致:
{{field}} es obligatorio只在字段标签是阳性时成立("Cuenta es obligatorio" 不对)。形容词要与中心词一致,而运行时标签无法声明性别,所以模板自带阳性中心词 ——El campo {{field}} es obligatorio,任何标签都成立。validation.unique同一处同一毛病,一并改。其余语言包未动。测试
新增 7 个文件里的 22 条断言,四项各自有钉子:
packages/fields/src/widgets/LookupField.gateHintLabel.test.tsx— 三个被门控的界面各断言一次(它们本来是同一个表达式的三份拷贝,只修一份从外面看不出来);另有两条回落断言(宿主没给映射时仍渲染 API 名),故意不具判别性,用来保住独立使用场景。packages/components/src/renderers/form/__tests__/form-depends-on-labels.test.tsx— form 到 widget 的管道;两侧的 strip(注册 widget 与 builtin 分支)都钉住。两个包不能互相 import(依赖方向如此),所以 widget 侧和 form 侧分开断言。packages/components/src/renderers/form/__tests__/form-invalid-toast-joiner.test.tsx— en 与 zh 两个"面"。packages/i18n/src/__tests__/validation-list-joiner-locale-parity.test.ts— 按文字体系钉住 joiner 的值(parity 测试只管 key 在不在,而这个 bug 从头到尾是值的问题;把 en 的", "抄进 zh 同样能过 parity 并重新弄坏中文)。packages/components/src/__tests__/page-header-more-actions-i18n.test.tsx、packages/plugin-detail/src/ReactionPicker.i18n.test.tsx— zh/ja/es 正向 + en 正向 + 英文字面量反向。反向验证(先写下预期方向,再跑)
预期:把 LookupField 那三处还原成
d.field,五条里三红两绿 —— 两条回落断言按设计不具判别性;把 joiner 还原成写死的、,en 那条红、zh 那条绿(zh 本来就是碰巧对的那一侧,它是回归护栏不是判别项)。实跑结果与预期逐条吻合:
受影响包全量
规则消费半径已扫:
lookup.selectFirst的调用方(LookupField、OptionsEmptyState、form.tsx)与validation.*的调用方全部落在上面四个包内,grep过packages/+apps/+e2e/,没有第五个包的 fixture 引用这些串。Changeset:
.changeset/console-chrome-i18n-5407.md(minor,按 AGENTS.md 版本策略绝不标major)。顺带开出的 issue(均未修进本 PR)
useDatasetFields.tsmake the whole file binary to grep objectstack#5425 —useDatasetFields.ts里两个裸 U+0000 字节,让整个文件对 grep 变成二进制ObjectGantthardcodes Chinese quick-filter labels — English-only-codebase violation, and the gantt toolbar is Chinese in every locale objectstack#5427 —ObjectGantt写死中文快筛标签metricpath bypassestWidgetTitle, and the plugin's privateresolveLabelnever callst()objectstack#5428 — dashboard KPI 卡丢掉已翻译的 widget 标题/描述(含一个待定的设计问题)Page header actions,Emoji picker, the reaction-count label,Close/Close panelin the nav overlay) objectstack#5430 — 其余未翻译的 chrome 无障碍名(含一个需要按本仓双 key 复数约定处理的)🤖 Generated with Claude Code
https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Generated by Claude Code