-
Notifications
You must be signed in to change notification settings - Fork 1
i18n: label 应为纯字符串,key 由框架自动生成 #1054
Description
问题背景
当前 I18nLabelSchema 定义为 string | { key, defaultValue, params } 联合类型,允许元数据的 label/description 字段以两种形态存在。这个设计导致了一系列连锁问题。
问题清单
1. React 渲染崩溃 (P0 — 已修复)
{ key, defaultValue } 对象被直接渲染为 React 子节点,触发 React error #31。
受影响文件:
apps/studio/src/components/MetadataInspector.tsx—label和description渲染apps/studio/src/components/app-sidebar.tsx— 元数据列表项 label 渲染
临时修复: 添加 resolveLabel() helper 将 i18n 对象降级为字符串。但这是 workaround,不是正解。
2. 两套翻译系统并行,职责不清
当前存在两套互不关联的 i18n 机制:
| 机制 | 存储位置 | 用于 | 示例 |
|---|---|---|---|
| TranslationBundle | 翻译文件 en.ts, zh-CN.ts |
对象 field label、选项值 | { objects: { account: { label: 'Account', fields: { ... } } } } |
| I18nObject | 内嵌在元数据定义中 | app/view/page 的 label | { key: 'setup.app.label', defaultValue: 'Setup' } |
- TranslationBundle 按
objects.<name>.fields.<field>.label结构化组织,由I18nServicePlugin加载并通过 REST API 提供 - I18nObject 散落在代码中,key 由开发者手写,没有对应的翻译文件,也没有被任何翻译服务管理
3. i18n key 命名无标准化
当前手写的 key 各自为政:
setup.app.label → SetupPlugin
setup.app.description → SetupPlugin
setup.nav.users → AuthPlugin
setup.nav.roles → SecurityPlugin
setup.nav.audit_logs → AuditPlugin
area 的 key:
setup.area.administration → setup-areas.ts
setup.area.platform → setup-areas.ts
这些 key 没有注册到翻译系统中,没有对应的翻译文件,defaultValue 永远不会被翻译覆盖。
4. 客户端没有 i18n resolution 能力
Studio 没有 i18n provider、没有 useTranslation hook、没有加载翻译资源。即使有翻译文件,客户端也无法根据 locale 解析 { key, defaultValue } — 只能退化为取 defaultValue。
5. API 响应格式不一致
同一个 GET /api/v1/meta/apps 接口:
- CRM app:
label: "Enterprise CRM"(字符串) - Setup app:
label: { key: "setup.app.label", defaultValue: "Setup" }(对象)
客户端必须对每个 label 做类型判断,增加了所有消费端的复杂度。
6. 使用 I18nLabelSchema 的 schema 范围广
以下 spec 中的 schema 都使用了 I18nLabelSchema,意味着所有这些元数据的 label 都可能是对象:
AppSchema.label,.descriptionNavigationArea.label,.descriptionBaseNavItemSchema.labelPageSchema.label,.descriptionDashboardWidgetSchema.title,.descriptionReportSchema.label,.descriptionChartSchema.title,.subtitleNotificationSchema.title,.messageKeyboardShortcutSchema.descriptionAriaPropsSchema.ariaLabel- 以及更多 UI schema
建议方案
核心原则:label 字段只接受 string,i18n key 由框架自动生成
Step 1: 标准化 i18n key 命名规则
<type>.<packageId>.<name>.label
<type>.<packageId>.<name>.description
<type>.<packageId>.<name>.navigation.<navId>.label
示例:
apps.com.objectstack.setup.setup.label → "Setup"
apps.com.objectstack.setup.setup.description → "Platform settings..."
apps.com.example.crm.crm_app.label → "Enterprise CRM"
objects.com.objectstack.system.sys__user.label → "User"
objects.com.objectstack.system.sys__user.fields.email.label → "Email"
Step 2: 注册时自动生成 key 并写入翻译服务
在 SchemaRegistry.registerApp() / registerItem() 中:
- label 存储为纯字符串(即 defaultValue)
- 自动生成标准 key,以 defaultValue 作为 fallback 写入
I18nService
Step 3: API 响应始终返回已解析的字符串
metadata REST endpoints 根据请求的 Accept-Language header 或 ?locale= 参数,调用 i18nService.t(key, locale) 解析后再返回。
Step 4: 废弃 I18nLabelSchema 联合类型
label,description等字段改为z.string()- 移除
I18nObjectSchema在 metadata schema 中的使用 - 开发者只需写
label: 'Setup' - 覆盖翻译通过翻译文件完成,而非内嵌 i18n 对象
迁移兼容性
- 现有使用字符串 label 的配置(CRM、Todo 等)无需任何改动
- 使用 i18n 对象的配置(SetupPlugin、AuthPlugin nav items)改为纯字符串
resolveLabel()workaround 可在迁移完成后移除
受影响的包
@objectstack/spec— I18nLabelSchema 定义和所有 UI schema@objectstack/objectql— SchemaRegistry 注册 + Protocol API 响应@objectstack/plugin-setup— setup-app.ts, setup-areas.ts@objectstack/plugin-auth— nav item labels@objectstack/plugin-security— nav item labels@objectstack/plugin-audit— nav item labels@objectstack/studio— MetadataInspector, app-sidebar 等所有渲染组件@objectstack/rest— metadata endpoint 响应格式