Skip to content

为翻译准确性开发“对象字段/选项完整性约束”与AI友好翻译骨架工具 #855

@hotlong

Description

@hotlong

背景

当前翻译文件(如 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[] }

校验维度:

  1. Zod 结构验证ObjectTranslationNodeSchema.safeParse()
  2. 字段完整性 — 源 fields 中有但翻译中没有 → 报 缺失字段
  3. 字段多余性 — 翻译中有但源 fields 中没有 → 报 多余字段
  4. Option 完整性 — select 字段的每个 option value 都必须有翻译
  5. 残留检查 — 不允许 __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: 文档更新(开发者指南)

Metadata

Metadata

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions