-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
enhancementNew feature or requestNew feature or request
Description
背景
当前翻译文件(如 examples/app-todo/src/translations/zh-CN.ts)使用 TranslationData 类型,底层是 z.record(z.string(), FieldTranslationSchema) —— 只验证值的形状,不验证 key 的完整性。
这意味着:
- AI 生成翻译时漏了 5 个字段,TypeScript 不报错,Zod 也不报错
- 多写了一个不存在的字段 key,也不报错
- select/multiselect 的 option 翻译少了几个,同样静默通过
参考现状:
- 对象定义
task.object.ts有 18 个字段,3 个 select 共 14 个 option - 翻译文件
zh-CN.ts只有TranslationData松散类型约束
目标
建立三重防线,保证 AI/人工翻译 字段全覆盖、option 不缺不漏、格式严格合规:
Object 定义 (task.object.ts)
│
├──→ ① TypeScript satisfies 约束 ← tsc 编译报错(少/多字段)
│
├──→ ② AI 翻译骨架 JSON(填空题) ← 结构已锁死,AI 不可能加减 key
│
└──→ ③ Vitest 完整性测试 ← CI 自动拦截
开发任务
Task 1: 类型工具 translation-typegen.ts
新增文件: packages/spec/src/system/translation-typegen.ts
从 Object 定义自动推导严格翻译类型:
// 核心泛型:把 fields Record 的 key 提取出来,要求翻译必须覆盖每一个
type StrictFieldTranslations<Fields> = {
[K in keyof Fields]-?: // -? = 去掉可选,变成必填
Fields[K] extends { options: ... }
? { label: string; options: Record<OptionValue, string> } // select 字段:options 也必填
: { label: string; help?: string; placeholder?: string } // 普通字段:label 必填
};
type StrictObjectTranslation<Obj> = {
label: string;
pluralLabel?: string;
fields: StrictFieldTranslations<Obj['fields']>;
};验收标准:
-
StrictObjectTranslation<typeof Task>可以正确推导出 18 个字段 key 为必填 - select 字段(status/priority/tags)的 option value 全部成为
options的必填 key - 少字段 → TS2741 报错;多字段 → TS2353 报错;少 option → TS2741 报错
- 从
packages/spec/src/system/index.ts导出 - 有单元测试验证类型推导正确性(利用
expectTypeOf或@ts-expect-error)
Task 2: 翻译骨架生成器 translation-skeleton.ts
新增文件: packages/spec/src/system/translation-skeleton.ts
从 Object 定义自动生成 AI 友好的 JSON 填空模板:
function generateTranslationSkeleton(objectDef: ServiceObject): string输出示例(task 对象):
{
"label": "__TRANSLATE__: \"Task\"",
"pluralLabel": "__TRANSLATE__: \"Tasks\"",
"fields": {
"subject": { "label": "__TRANSLATE__: \"Subject\"" },
"status": {
"label": "__TRANSLATE__: \"Status\"",
"options": {
"not_started": "__TRANSLATE__: \"Not Started\"",
"in_progress": "__TRANSLATE__: \"In Progress\"",
"waiting": "__TRANSLATE__: \"Waiting\"",
"completed": "__TRANSLATE__: \"Completed\"",
"deferred": "__TRANSLATE__: \"Deferred\""
}
}
}
}验收标准:
- 输入
task.object.ts的对象定义 → 输出包含全部 18 个字段的骨架 - select 字段自动提取所有 option value 并生成
options映射 - 有
description的字段自动生成help占位符 - 输出是合法 JSON,可直接被
ObjectTranslationNodeSchema.parse()验证(替换占位符后) - 有单元测试
Task 3: 完整性校验函数 validateTranslationCompleteness()
新增文件: packages/spec/src/system/translation-validator.ts
function validateTranslationCompleteness(
objectDef: ServiceObject,
translation: unknown,
): { valid: boolean; errors: string[] }校验维度:
- Zod 结构验证 —
ObjectTranslationNodeSchema.safeParse() - 字段完整性 — 源 fields 中有但翻译中没有 → 报
缺失字段 - 字段多余性 — 翻译中有但源 fields 中没有 → 报
多余字段 - Option 完整性 — select 字段的每个 option value 都必须有翻译
- 残留检查 — 不允许
__TRANSLATE__占位符残留
验收标准:
- 缺 1 个字段 → errors 包含该字段名
- 多 1 个字段 → errors 包含该字段名
- 缺 1 个 option → errors 包含
fields.{name}.options.{value} -
__TRANSLATE__残留 → errors 报告 - 有完整单元测试
Task 4: 改造 Todo 示例作为范例
修改文件: examples/app-todo/src/translations/zh-CN.ts
// Before:
import type { TranslationData } from '@objectstack/spec/system';
export const zhCN: TranslationData = { ... };
// After:
import type { StrictObjectTranslation } from '@objectstack/spec/system';
import { Task } from '../objects/task.object';
type TaskTranslation = StrictObjectTranslation<typeof Task>;
export const zhCN = {
objects: {
task: { ... } satisfies TaskTranslation,
},
// ...
};验收标准:
-
tsc --noEmit编译通过 - 故意删掉一个字段 →
tsc报错(在测试中用@ts-expect-error验证)
Task 5: Vitest 完整性测试
新增文件: examples/app-todo/src/translations/translation-completeness.test.ts
const fieldNames = Object.keys(Task.fields);
const selectFields = Object.entries(Task.fields)
.filter(([_, f]) => Array.isArray(f.options))
.map(([name, f]) => ({ name, values: f.options.map(o => o.value) }));
describe.each([['en', en], ['zh-CN', zhCN]])('%s', (locale, t) => {
it.each(fieldNames)('field: %s', (name) => {
expect(t.objects?.task?.fields?.[name]?.label).toBeTruthy();
});
it.each(selectFields)('options: $name', ({ name, values }) => {
for (const v of values) {
expect(t.objects?.task?.fields?.[name]?.options?.[v]).toBeTruthy();
}
});
});验收标准:
-
pnpm test -- translation-completeness全部通过 - 故意删掉一个 option 翻译 → 测试失败并明确指出哪个 option 缺失
不涉及(Not in scope)
- 不修改现有 Zod Schema(
z.record保持不变) - 不修改现有
TranslationData/AppTranslationBundle类型(向后兼容) - 不涉及 views/actions/workflows 的翻译完整性(后续迭代)
- 不涉及 CLI 包的实际
objectstack命令注册(本期只提供函数,CLI 集成后续)
相关文件
| 文件 | 操作 | 说明 |
|---|---|---|
packages/spec/src/system/translation-typegen.ts |
新增 | 严格类型推导工具 |
packages/spec/src/system/translation-skeleton.ts |
新增 | AI 骨架生成函数 |
packages/spec/src/system/translation-validator.ts |
新增 | 完整性校验函数 |
packages/spec/src/system/index.ts |
修改 | 新增 export |
examples/app-todo/src/translations/zh-CN.ts |
修改 | satisfies 范例 |
examples/app-todo/src/translations/translation-completeness.test.ts |
新增 | 完整性测试 |
子任务拆分建议
- Task 1:
translation-typegen.ts类型推导 - Task 2:
translation-skeleton.ts骨架生成 - Task 3:
translation-validator.ts校验函数 - Task 4: Todo 示例改造 + satisfies 落地
- Task 5: Vitest 完整性测试
- Task 6: 文档更新(开发者指南)
Reactions are currently unavailable
Metadata
Metadata
Labels
enhancementNew feature or requestNew feature or request