AI-3.5-02 动态内容 AI 翻译 · 实现策略 TDD

商品与分类内容中日英三语化——「管道增强段(无门控)+ 服务接入段(DEC-1/C2 门控)」两段式设计(PRD 需求编号 FR-3.5-02)

类型:技术设计文档(TDD) 条目编号:AI-3.5-02(溯源 FR-3.5-02) PRD 版本:V1.4(2026-10-22,C1 已更正口径) 优先级:Must(P1:部分实现,补齐关键缺口) 深度分级:full full 波次:pilot(打样波) 文档版本:v1.0(2026-10-25) 源文件:docs/reports/ai-module-prd/tdd/AI-3.5-02-dynamic-content-translation.md

摘要

PRD V1.4 §5.4 FR-3.5-02 验收要点(原文):「译文抽检合格率与失败重试成功率跟踪(基线立项定);AI 服务接入启动与扩量严格按 C2 门槛执行(接入时点见 DEC-1)。」 需求本体:商品、商品分类等业务内容在发布后自动进行中日英三语 AI 翻译,前台按用户语言展示对应译文——商家只发一种语言,全球买家均可以母语浏览商品信息。 本条底座是三语检索(FR-3.3-02)与新零售跨语运营的底座;当前翻译范围限商品与商品分类的结构化内容(标题、描述等)。

两态口径(不得混同,承 PRD V1.4):①底座已建成——翻译参数配置页(密钥加密存储)、失败自动重试队列、状态提示、自动化测试均在; ②AI 服务未接入——生产未配置任何 AI 服务(2026-10-21 部署权威口径,风险 C1 已更正为「已确认未接入」)。 代码层基线:平台唯一真实 AI 通路为 backend/src/modules/translation/client.ts(586 行,OpenAI 兼容客户端,SSRF 三层防护 + 60s 超时), dev 环境已接内网 LLM(TRANSLATION_AI_BASE_URL=http://192.168.66.225:3030/v1),生产 .env.template 为占位值。 因此本 TDD 基线=增强现有翻译管道,非从零新建。

全文以两段式组织(强制拆分):管道增强段(P-①~⑤,无门控、零外部依赖,pilot 波立即可开工)与 服务接入段(生产接入步骤链、category 预检对齐、专属单测、抽检挂接点,DEC-1/C2 门控)。 接入顺序不可颠倒:先关 C2 合规条款(AI-3.9-01 专项条款发布)→ DEC-1 定接入时点 → DEC-2 定扩量节奏。

两段式结构
5 + 3
管道增强段 P-①~⑤ 零门控立即可开工;服务接入段 3 项待 DEC-1+C2 关闭(门控)
验收标准
9
A1 重试成功率可观测 … A9 故障演练主链路零阻断(NFR-3.8-01)
缺口清单
8
①~⑦ findings 缺口 + ⑧ 吞吐量化口径(探索新发现),逐条→目标状态
数据模型变更
1 新表
ai_call_log 从零新建(usage 审计,闭环 NFR-3.9-03);translation_config 仅加 1 列;translation_job 零变更

关键概念

两态口径
「底座已建成」与「AI 服务未接入」不得混同(承 PRD V1.4,风险 C1 已更正)。现状判定引用代码证据而非复述 PRD:唯一真实 AI 通路 client.ts,生产默认未配置。
管道增强段 / 服务接入段
本 TDD 的强制两段式:管道增强段(P-①~⑤)无门控零外部依赖;服务接入段以 DEC-1(接入时点)+C2(合规前置)关闭为开工前置,顺序不可颠倒。
双防线跳过
防线一:isAIConfigured 入口预检(探测自身异常时返回 true 保 C6 永不阻断);防线二:retry worker 内 markSkippedNoConfig 兜底终态化——NFR-3.8-01 强制范式。
translation_job 重试队列
幂等 enqueue + claimed_at 原子占位 + 指数退避 2^retry_count 分钟 + MAX_TRANSLATION_RETRY=5 + 全局并发信号量 3 + 5 分钟 cron 轮询。
enc:v1: 与 AES-256-GCM
api_key 密文格式 enc:v1:+base64(iv‖tag‖ciphertext)。现状密文无独立版本标记层、解密对无前缀旧明文透明返回——轮换密钥即存量密文不可解(P-③ 以 enc:v2: 双密钥「读旧写新」根除)。
ai_call_log
新建 AI 调用审计实体(P-④)。现状 client.ts 对响应 usage 字段从不读取(rg 0 命中)——即 NFR-3.9-03「调用量与成本统计尚无」在代码层的丢弃点。
fillPrompt 扩展位
{source}/{target} 占位替换+空模板回退内置指令(client.ts:295-306 复核精确)。场景化 prompt 路由与术语注入属 AI-3.5-03 范围,本卡仅注明现状。
NFR-3.8-01 范式
AI 可选增强不阻断主业务——本条目全部设计的强制范式:未配置态跳过、故障态进重试队列、storefront 译文缺失回退原语言,主流程(商品发布/分类创建)零阻断。

方案设计

两段式总览(承两态口径的强制拆分)

分段门控内容开工条件
管道增强段 无门控 零外部依赖 P-① 白名单参数化 / P-② 状态查询 API / P-③ 加密强制+密钥轮换 / P-④ usage 审计采集 / P-⑤ 吞吐背压指标位 立即可开工(pilot 波)
服务接入段 DEC-1 + C2 门控 生产接入步骤链、category 预检对齐、run-translate-category 专属单测、译文抽检挂接点 前置决策点 DEC-1/C2 关闭后方可开工(顺序不可颠倒)
flowchart TD START["pilot 波启动"] --> P["管道增强段(无门控,立即可开工)"] P --> P1["P-① 字段白名单参数化"] P --> P2["P-② 翻译状态查询 API+banner 升级"] P --> P3["P-③ 加密强制+密钥轮换(RK-02)"] P --> P4["P-④ usage 审计采集 ai_call_log"] P --> P5["P-⑤ 吞吐/背压指标位(RK-09)"] P --> G1{"C2 合规条款关闭?(AI-3.9-01 专项条款发布)"} G1 -- "否:顺序不可颠倒" --> WAIT["服务接入段保持关闭(设计备妥不实施)"] G1 -- "是" --> G2{"DEC-1 接入时点到达?"} G2 -- "是" --> ACC["服务接入段开工(扩量按 DEC-2 节奏)"] ACC --> A1["生产配置+灰度开闸(先分类后商品)"] ACC --> A2["category 触发链预检对齐"] ACC --> A3["run-translate-category 专属单测"] ACC --> A4["译文抽检挂接点接线(→AI-3.5-04)"]
图 1. 两段式开工条件——管道增强段无门控立即可开工;服务接入段必须等 C2 条款关闭 → DEC-1 时点(顺序不可颠倒)(转绘自源 §4.0/§4.2,Inferred)

设计红线(AGENTS.md)

  • Workflow 承载业务逻辑——本卡全部数据变更走 Workflow/step(加密守卫在 step 内实现,路由层不改);
  • 不修改上游源码——@medusajs/translation 与 @mercurjs/* 仅经官方扩展点;本卡对 client.ts/config/job 均为本地模块内扩展,不触碰上游包;
  • 后端是单一数据源——队列状态、配置、审计均以后端为准,admin 仅消费;
  • 动态 SQL 参数化绑定——现有 q ILIKE 已示范参数化,本卡无新增裸 SQL。

管道增强段(无门控,立即可开工)

P-① 字段白名单参数化(缺口②)

现状:可翻译字段白名单写死在 step 工具内(collect-product-input.ts,卡内 :16-44 / 白名单判定实际 :36-60)。

层级现状硬编码白名单回写条件
商品级字段title / subtitle / description值非空即纳入
option 级字段options[].titleoption 有 id(拿不到 id 无法回写译文,降级跳过不 fail)
option value 级字段options[].values[].valuevalue 有 id
分类(另一工具内)name / description(run-translate-category.ts buildFields :87-93,description 空则丢弃)值非空
候选说明评估
A. translation_config 表新增 translatable_fields JSONB 列 与现有「DB 优先+env 回退」配置模型(service.ts:22-32)同构;admin 现有配置页可直接扩展 ✅ 选定 admin 可视化维护零新增界面骨架;单行 key="default" 结构天然承载;沿用 maskApiKey 同页管理
B. 常量模块+环境变量 零 migration,但改白名单需发版+env 泛滥 ❌ 运营不可自助,与「配置页可视化」产品方向相悖
  • translation_config.translatable_fields:jsonb NULL。NULL=回退内置默认集(向后兼容,存量行为零变化);
  • JSON schema(zod 校验,键白名单闭合——只允许代码已知且能回写译文的字段,防止配置进未支持字段后 AI 产出无处落):
{
  "product": {
    "fields": ["title", "subtitle", "description"],
    "options": { "title": true },
    "option_values": { "value": true }
  },
  "product_category": { "fields": ["name", "description"] }
}
  • 内置默认集(backend/src/utils/translation-field-whitelist.ts 新常量)=现状硬编码清单的参数化等价物,保证「配置为空 → 行为与今天逐字节相同」;
  • 读取链:collectProductInput(product, sourceLocale, whitelist?) 增加可选参——由 run-translate-product step 从 getEffectiveConfig() 取 translatable_fields ?? DEFAULT_TRANSLATABLE_FIELDS 传入;category 路径同理读 product_category 键;
  • 前端:translation-config-form.tsx 增加白名单编辑区(字段多选+非法键禁用),提交并入现有 POST /admin/translation-config(validators.ts zod schema 扩展,服务端二次校验键闭合)。
变更生效语义(显式声明)

白名单变更仅影响后续触发的翻译,不对存量商品自动全量重译(避免误操作触发大规模 AI 调用;如需重译走 admin 手动批量通道)。

P-② 翻译状态查询 API + banner 升级(缺口③)

新增路由:GET /admin/translations/status(新建 backend/src/api/admin/translations-status/route.ts——如与上游 @medusajs/translation 的 /admin/translations/* 路由命名冲突,回退名 GET /admin/translation-jobs/status;以任务卡指定名为准并留冲突注记)。

  • 权限域:admin 侧(与 /admin/translation-config 同级,走 /admin 全局认证中间件;query 校验用 validateAndTransformQuery);
  • 聚合来源:translation_job 模块(本地模块,:157 注册 isQueryable: true)——不改上游 @medusajs/translation。
{
  "queue": {
    "pending": 0,             // status='pending' 计数
    "processing": 0,          // status='processing'
    "done": 0,                // status='done'(近 7 天窗口,query 参数 window_days 默认 7)
    "failed": 0,              // status='failed'(达 MAX_TRANSLATION_RETRY=5 上限)
    "retry_due_now": 0,       // pending 且 next_retry_at <= now()
    "oldest_pending_at": null // 最老 pending 的 created_at(积压时长感知)
  },
  "by_reference": {
    "product":          { "pending": 0, "processing": 0, "failed": 0 },
    "product_category": { "pending": 0, "processing": 0, "failed": 0 }
  },
  "failure_top_errors": [     // 近窗口 last_error 聚合 top 5(截断展示)
    { "last_error": "AI translation config not set", "count": 12 }
  ],
  "retry_success_rate": null, // done/(done+failed) 窗口内;样本不足为 null——映射「失败重试成功率跟踪」
  "ai_config": {
    "configured": true,       // 复用 isAIConfigured 语义(utils/translation-config.ts:49-66)
    "encryption_enabled": true // isEncryptionConfigured()(P-③ 状态透出)
  },
  "generated_at": "2026-10-25T00:00:00Z"
}
  • 错误码:401 未认证 / 403 非 admin / 400 window_days 非法(1-90)/ 500 聚合失败(读路径不写库,无事务);
  • 前端升级:translation-config-status-banner.tsx(:40-57 现状仅 isFullyConfigured 二态 Alert)升级为「配置态+队列态」复合横幅:新增 admin-panel/src/hooks/api/translation-status.ts(React Query,60s 轮询),展示 pending/failed 计数与最老积压;AI 未配置时保持现有 not-configured 文案(横幅只是可观测性,不产生任何阻断行为)。

P-③ 加密强制 + 密钥轮换(RK-02 / R-encryption-fallback 缓解,缺口⑥)

现状缺陷(两处):

  1. 明文降级无守卫:src/workflows/translation-config/steps/upsert-translation-config.ts:52-56——isEncryptionConfigured() ? encryptSecret(...) : input.api_key,生产未设 TRANSLATION_ENCRYPTION_KEY 时 api_key 明文落库(无 NODE_ENV=production 守卫);
  2. 密文无版本标记层:backend/src/utils/crypto/secret-crypto.ts——decryptSecret(:92)对无前缀旧明文透明返回(:89-95),且解密仅认单一 v1 形态——轮换密钥即存量密文全部不可解。

设计 1:生产加密守卫(两阶段灰度)

  • 阶段 1(告警):step 内检测 NODE_ENV=production && !isEncryptionConfigured() && input.api_key 非空 → logger.error 告警但仍落库(灰度观察期);
  • 阶段 2(强制):同条件改为 Workflow step 内抛 MedusaError(Types.INVALID_DATA, "TRANSLATION_ENCRYPTION_KEY is required in production"),拒绝落库不降级——守卫在 step 内实现(AGENTS.md 红线),上游 route 无需改动;
  • 灰度开关:env TRANSLATION_ENCRYPTION_REQUIRED(缺省 false=阶段 1,置 true=阶段 2);
  • 存量明文检测与迁移:一次性 workflow migrate-plaintext-api-keys——扫描无 enc:v1: 前缀的非空 api_key 行 → encryptSecret 回写;幂等(有前缀跳过);dev 环境跳过。

设计 2:密钥轮换(enc:v2: 双密钥读旧写新)

  • secret-crypto.ts 扩展:新前缀 enc:v2: 与 KEY_SALT_V2(派生盐换版本串,隔离 KDF 域);resolveKey(version)——v1 读 TRANSLATION_ENCRYPTION_KEY,v2 读 TRANSLATION_ENCRYPTION_KEY_V2(未设则 v2 写操作抛错);
  • decryptSecret 按前缀路由解密密钥(enc:v2:→新钥,enc:v1:→旧钥,无前缀→明文透明返回兼容不变);读旧写新:写路径在 V2 密钥存在时一律产出 enc:v2:——「密文无版本标记、轮换即存量不可解」由「前缀→密钥版本路由」根除;
  • 轮换 workflow:rotate-translation-encryption-key(steps 链见 Workflow 设计表)。

轮换操作步骤(运维 SOP,随 TDD 交付):

  1. 配置 TRANSLATION_ENCRYPTION_KEY_V2(新钥)并重启;
  2. 触发轮换 workflow:逐行读存量 enc:v1: 密文 → 用旧钥解密 → enc:v2: 重加密回写(分批+幂等+失败行留痕);
  3. 全量行核验为 enc:v2: 后,将新钥值挪到 TRANSLATION_ENCRYPTION_KEY、清空 _V2(或保持双变量、以 v2 为主——两形态等价,取其一并在 runbook 固化);
  4. 旧密钥仅在「回滚窗口」内保留(见迁移与回滚)。
兼容注记

dev .env 未配置加密主键(明文态)不受影响;轮换属管道增强段,与 AI 服务是否接入无关。

P-④ AI 调用审计 usage 采集(闭环 NFR-3.9-03,缺口⑦)

现状:callChatCompletions(client.ts :369-400)响应的 usage(prompt_tokens/completion_tokens/total_tokens)从不读取(rg 0 命中),成功/失败调用均无留痕——PRD NFR-3.9-03 明示「AI 调用专项审计(调用量与成本统计)尚无」为待建项。

新实体 ai_call_log(新本地模块 ai-call-log,表 ai_call_log)字段级:

字段类型约束/默认说明
idtextPK,prefix aicall
request_idtextnullable调用关联 id(每次 translateWithAI 批次生成,跨 locale 复用)
modeltextnot nullconfig.model
provider_hosttextnot nullbase_url host:port(排障用,无 PII)
localetextnot null目标 locale(ja-JP/zh-Hans/en-US)
referencetextnot nullproduct/product_category(对齐 TRANSLATION_REFERENCES :20-25)
reference_idtextnot null实体 id
statusenum(success, error)not null, default success该次单 locale 调用结果
prompt_tokensintegernullable响应 usage.prompt_tokens;provider 未返回/解析失败为 null
completion_tokensintegernullable同上
total_tokensintegernullable同上
duration_msintegernot null, default 0请求起止耗时
error_summarytextnullable失败原因(截断 500 字,复用 last_error 截断范式)
created_at / updated_at / deleted_attimestamptz标准三件套medusa 约定
  • 索引:(reference, reference_id)、(created_at)、(model)、(status);migration 走 medusa db:generate;
  • 采集点设计(最小侵入):client.ts 为纯函数文件夹、已有 container 入参——在 translateWithAI 单 locale 请求完成/抛错处补 recordAiCallLog(container, entry);落库走新 ai-call-log 模块 service;写失败仅 logger.warn,不阻断翻译主流程(C6 同构);不修改上游 @medusajs/translation(本卡全部 AI 流量走自研 client,采集覆盖完整)。
范围声明

译文版本历史(译文被覆盖可回溯)属 NFR-3.9-03 的另一待建项,归 AI-3.5-04 复核工作流范围,本卡不设计(引用不展开)。

P-⑤ 吞吐/背压指标位(RK-09 缓解,缺口⑧)

现状参数:并发信号量 3(retry-translations.ts:60-68)、轮询 cron 5min(constants.ts:13)、指数退避 2^retry_count 分钟(constants.ts:16)、重试上限 MAX 5(constants.ts:10)。验收口径必含四个指标名(数值一律「基线立项定(DEC-5)」,不臆造):

指标定义现成数据源
队列积压深度pending 计数+最老 pending 存续时长P-② API queue 段
单任务端到端时延enqueue→done 的时长分布translation_job created_at/updated_at 差值(P-② 聚合扩展位)
重试成功率窗口内 done/(done+failed)P-② API retry_success_rate
理论吞吐上限并发 3 × (单任务时长)^-1ai_call_log.duration_ms 聚合(P-④ 落地后可算)

全量商品回填积压规模估算公式(运营预案用):积压规模 ≈ 商品数 × 语种数(=2) ÷ 日吞吐;日吞吐由第四项实测后回填——基线立项定(DEC-5)。

服务接入段(DEC-1/C2 门控,关闭后方可开工)

门控声明(顺序不可颠倒)

本段设计照写备妥,但任何实施动作(生产配置、触发真实 AI 调用、扩量)必须等:① C2 合规条款关闭(AI-3.9-01 专项条款发布,先关条款后接入)→ ② DEC-1 定接入时点 → ③ 扩量节奏按 DEC-2。管道增强段(P-①~⑤)不受本门控约束。

生产接入步骤链(每步可独立验证,顺序硬约束):

  1. C2 前置(不可跳过):隐私政策 AI 处理专项条款发布并生效(内容与双分支归属归 AI-3.9-01/法务,本卡只挂前置);验收=条款线上可见+分支归属回填(DEC-2 关联);
  2. 生产配置:backend/.env 按 .env.template:91-94 填入真实 TRANSLATION_AI_BASE_URL/API_KEY/MODEL(ALLOW_PRIVATE_HOST 必须留空——模板 :88-90 注释明示生产 SSRF 防护依赖此项,且误设时 NODE_ENV=production 双重门控拦截);同时确认 P-③ 加密守卫已处强制态;
  3. DEC-1 时点到达后灰度开闸:先分类(量小)后商品,观察 P-② 队列面板与 P-⑤ 指标;
  4. DEC-2 节奏扩量:扩量速率受 C2 分支归属与吞吐基线约束;全量回填前按 P-⑤ 公式预估积压。

接入段补两个缺口(缺口④⑤收口):

  • category 触发链对齐:category-created-translation.ts hook 现直调 translateCategoryToLocales(无预检)→ 对齐 product 范式:hook 内先 isAIConfigured 预检(异常返回 true 不阻断)→ enqueue 进 translation_job(reference=product_category 已在白名单 :35-42,retry worker 的 retryCategory :278-284 已就绪)——category 路径全程 C6 不阻断建类;
  • 补 run-translate-category.unit.spec.ts 专属单测:覆盖正常翻译/字段缺失跳过/AI 异常 enqueue/未配置降级四分支。

译文抽检挂接点(PRD 验收要点「译文抽检合格率」载体预留):在翻译持久化 step 与 translation.product.updated 事件之间预留 sample-review 扩展点(采样率/合格口径基线立项定(DEC-5);复核工作流本体归 AI-3.5-04,交叉引用不重复设计)。

管道时序 · 状态机 · 实体图

sequenceDiagram autonumber participant EV as 触发源(product.created / categoriesCreated hook) participant SUB as subscriber 入口预检 participant U as translation-config utils participant J as translation_job 队列 participant W as retry-translations worker participant C as client.ts(AI 通路) participant T as 上游 Translation Module EV->>SUB: 发布/建类事件(纯 enqueue 转发,不在 subscriber 内调 AI) SUB->>U: isAIConfigured 预检(防线一,:49-66) alt 未配置(base_url/api_key/model 任一缺) SUB-->>EV: 跳过 enqueue,主流程零感知 else 探测自身异常(catch 返回 true,:63-65) SUB->>J: 回退 enqueue 路径(C6 永不阻断) else 已配置 SUB->>J: enqueueTranslationJob(幂等去重:同 reference+id 活跃任务跳过) end W->>J: cron 5min 轮询+claim(claimed_at 原子占位,并发信号量 3) W->>U: isAIConfigured 再探测(防线二,:205-215) alt 未配置 W->>J: markSkippedNoConfig 终态化(failed+no-config 前缀,logger.info 非告警) else AI 调用失败 W->>J: 重试排队(指数退避 2^n 分钟,MAX 5,达上限告警 attempts=N/5) else 成功 W->>C: callChatCompletions(SSRF 三层防护+60s 超时+temperature 0.3) C-->>W: choices[0].message.content(usage 字段被丢弃→P-④ 采集落 ai_call_log) W->>T: 落译文(上游模块 upsert) W-->>EV: emit translation.product.updated(直接 emit,前端 revalidate 消费) end Note over EV,T: 任一环节 AI 异常均不阻断主业务(NFR-3.8-01 / C6)
图 2. 翻译管道执行时序——双防线跳过(入口预检+兜底终态化)与重试退避(转绘自源 §2.1④/§5.3,Inferred)
stateDiagram-v2 state "pending(待处理)" as pending state "processing(处理中)" as processing state "done(成功)" as done state "failed(失败终态)" as failed [*] --> pending: enqueueTranslationJob(幂等去重) pending --> processing: worker claim(claimed_at 原子占位) processing --> done: 译文落库成功 processing --> pending: AI 调用失败——指数退避 next_retry_at = now + 2^retry_count 分钟 processing --> failed: 达 MAX_TRANSLATION_RETRY=5(告警日志 attempts=N/5) pending --> failed: markSkippedNoConfig(AI 未配置兜底终态化,failed+no-config 前缀) done --> [*] failed --> [*]
图 3. translation_job 状态机与重试退避(status enum:pending/processing/done/failed;参数源 constants.ts 与 retry-translations.ts;转绘自源 §2.1③,Inferred)
erDiagram TRANSLATION_CONFIG ||--o{ TRANSLATION_JOB : "生效配置驱动队列消费" TRANSLATION_JOB ||--|| TRANSLATION : "reference + reference_id 弱关联译文(上游模块)" TRANSLATION_CONFIG ||--o{ AI_CALL_LOG : "model 与 provider_host 来源" AI_CALL_LOG }o--|| TRANSLATION_JOB : "request_id 关联同批调用" TRANSLATION_CONFIG { text id PK "prefix trcfg" text key UK "default 唯一(单行表)" text base_url "OpenAI 兼容端点" text api_key "enc:v1: AES-256-GCM" text model "如 deepseek-v4-flash" text prompt "全局模板 fillPrompt" integer max_tokens "default 4096" jsonb translatable_fields "P-① 新增 NULL=默认集" } TRANSLATION_JOB { text id PK "prefix trjob" text reference "product 或 product_category" text reference_id "实体 id" jsonb target_locales "目标语言数组" text status "pending processing done failed" timestamptz claimed_at "原子占位 nullable" integer retry_count "default 0" text last_error "nullable 截断" timestamptz next_retry_at "指数退避" } AI_CALL_LOG { text id PK "prefix aicall" text request_id "批次关联 nullable" text model "调用模型" text provider_host "host:port 无 PII" text locale "目标 locale" text reference "对齐 TRANSLATION_REFERENCES" text reference_id "实体 id" text status "success 或 error" integer prompt_tokens "usage 采集 nullable" integer completion_tokens "usage 采集 nullable" integer total_tokens "usage 采集 nullable" integer duration_ms "请求耗时 default 0" text error_summary "失败原因截断 500 字" }
图 4. 翻译域实体关系——translation_config(配置)/ translation_job(队列)/ ai_call_log(审计,P-④ 新建)(按源 §2.1/§4.1 字段表转绘,Inferred)

数据模型 / API / Workflow

数据模型设计汇总(字段级)

对象变更字段级内容
translation_config(现有表) 新增 1 列 translatable_fields jsonb NULL(P-①;NULL=内置默认集)
ai_call_log(新表/新模块) 新建 14 字段全列见 P-④ 字段表(id/request_id/model/provider_host/locale/reference/reference_id/status/prompt_tokens/completion_tokens/total_tokens/duration_ms/error_summary/时间三件套)+ 4 索引
translation_job(现有表) 无变更 P-② 仅读聚合;字段已在 models/translation-job.ts:44-68(migration Migration20260623184915.ts:6)
module link 无新增 translation_job 与译文本体(上游 Translation Module)经 reference/reference_id 弱关联(模型头注释 :5-8),维持不变

API 契约汇总

路由方法变更权限域要点
/admin/translations/status GET 新增(P-②) admin 响应/错误码见 P-②;只读聚合无副作用;上游命名冲突注记→回退名 /admin/translation-jobs/status
/admin/translation-config GET/POST/PUT 行为变化(P-①③) admin POST/PUT 请求体新增可选 translatable_fields(zod 键闭合校验);生产未配加密钥时按灰度阶段 warn/抛错(守卫在 workflow step 内,路由层不改)
/admin/translations、/admin/translations/batch GET/POST 无改动 admin 上游 @medusajs/translation 提供(medusa-config.ts:239;admin hooks translations.ts:92 消费),本卡不触碰

Workflow 设计(业务逻辑必须在 Workflow)

Workflow状态steps 链补偿逻辑事件
upsert-translation-config(现有) 增强(P-①③) upsert-translation-config-step(:40-70 已有 previous 快照返回)内嵌:加密守卫(阶段1 warn/阶段2 抛错)+ translatable_fields 校验 已有:StepResponse 携 {created, id, previous},compensation 回写 previous(现有结构沿用) 无新增
rotate-translation-encryption-key 新增(P-③) ① list-v1-rows(扫描 enc:v1: 行,分批游标)→ ② re-encrypt-batch(旧钥解→v2 密回写,幂等:v2 前缀跳过)→ ③ verify-all-v2(全量核验,失败行留痕不抛)→ ④ emit-rotation-report ② 失败批:保留原密文不动(读旧写新保证可重入);③ 不通过则报告列出失败行,不自动回写 完成后 event bus emit translation.encryption.rotated(直接 emit,理由同 run-translate-product :251-256)
migrate-plaintext-api-keys 新增(P-③) ① find-plaintext-rows(无 enc: 前缀非空 api_key)→ ② encrypt-batch(幂等) 单行失败跳过留痕 完成日志报告
run-translate-product / run-translate-category(现有) P-① 接入白名单参数 translateProductAsync/translateCategoryToLocales 读 effective config 的 translatable_fields 既有 catch→enqueue(runTranslationWithRetry 承载) translation.product.updated(:254,直接 emit)
category 触发链改造(接入段) hook→预检→enqueue 见服务接入段(isAIConfigured 预检→enqueue) C6 try/catch 不阻断建类 复用 job 队列

前端改动面与 i18n

前端改动面(逐端)

端改动依据
admin-panel ① translation-config-status-banner.tsx:静态二态→配置态+队列态复合横幅(新增 hooks/api/translation-status.ts,60s 轮询);② translation-config-form.tsx:新增 translatable_fields 编辑区;③ translation-section/translation-editor:无逻辑改动(批量通道不变) P-①②;现挂载点 product-detail/category-detail/article-form 不变
vendor-panel 无改动 rg「translation」仅命中 i18n 框架文件,无翻译业务面;商家侧译文消费走 storefront API
influencer / referrer panel 无改动 无翻译业务触点(本卡改动收敛于 admin 与后端)
storefront(B2C) 无改动 译文经 Medusa store API 按 x-medusa-locale 透出(storefront/src/lib/data/products.ts 已传 locale);P-② 的新 API 为 admin 域
b2b / h2b storefront 无改动 两端 products.ts x-medusa-locale 0 命中——「B2B 前台始终显示原语言」属 AI-3.3-02 三语检索条目范围,本卡不越界

i18n 三语言 key 规划(en / zhCN / ja)

改动面仅 admin-panel:admin-panel/src/i18n/translations/en.json、zhCN.json、ja.json 三文件同步新增(三语值随实现提交;遵循现有 translationConfig.* 命名空间):

新增 keyen(示意)用途
translationConfig.fields.title / .description / .options.title / .optionValues.valueTranslatable field labelsP-① 白名单编辑区
translationConfig.fields.resetToDefaultReset to default set回退默认集按钮
translationConfig.encryption.notConfiguredWarningEncryption key not set — API key will be stored in plain text阶段 1 告警横幅
translationConfig.encryption.requiredErrorEncryption key is required in production阶段 2 报错文案
translationConfig.encryption.rotationActionRotate encryption key轮换入口(运维角色可见)
translationConfig.status.queue.pending / .failed / .oldestpending / failed / oldest pendingP-② 队列横幅
translationConfig.status.retrySuccessRateRetry success rateP-② 指标展示

storefront / b2b / h2b / vendor i18n 无新增 key(无 UI 改动)。

验收与测试

验收标准(PRD 要点+技术口径)

#验收标准映射
A1失败重试成功率可在管理端观测(P-② retry_success_rate 字段),口径与数值基线立项定(DEC-5)PRD 验收要点①
A2译文抽检合格率:抽检机制挂接点就绪;合格率数值与采样率基线立项定(DEC-5)(本体归 AI-3.5-04)PRD 验收要点①
A3接入启动与扩量严格按 C2 门槛执行:接入段步骤链顺序不可颠倒可审计(条款生效时间戳 < 首次生产 AI 调用时间戳)PRD 验收要点②
A4生产环境 TRANSLATION_ENCRYPTION_KEY 未设时 api_key 拒绝落库(阶段 2 强制态验证);存量明文迁移后 0 行无前缀RK-02 缓解验收
A5密钥轮换演练:v1 存量 100% 轮换为 v2 后,配置读取/翻译全链路无感;轮换中任一时刻 decrypt 可用P-③ 验收
A6ai_call_log 覆盖率:自研通路 AI 调用 100% 留痕(成功+失败);NFR-3.9-03 口径达标值立项定缺口⑦验收
A7白名单参数化:改配置后新触发翻译按新白名单执行;清空配置回退默认集与现状行为逐字节一致缺口②验收
A8吞吐/背压四指标(积压深度/端到端时延/重试成功率/理论吞吐)有采集位与展示;数值基线立项定(DEC-5)RK-09 验收
A9故障演练:模拟 AI 服务异常,商品发布/分类创建主链路零阻断(NFR-3.8-01 验收要点原文)5.3 测试点 2

测试策略(分层)与 L0 门禁

L0 门禁(本 Session TDD 打样最低自动化门槛)

本文档十要素章节齐备、关键 code anchor 字符串可 rg 命中、既有翻译域 8 个单测可回归通过(实现期复跑)。

层范围明细
既有回归(8 文件) 现网行为守卫 client / ssrf / translation-config.service / mark-skipped-no-config / retry-translations / translation-updated-revalidate / translation-config(utils) / run-translate-product
新增单测(管道段) P-①~④ ① 白名单参数化:默认集等价性/非法键拒绝/配置覆盖;② status API:聚合正确性/窗口参数/权限;③ 加密守卫(两阶段)+轮换(v1→v2→读旧写新→回滚)+明文迁移幂等;④ usage 采集:成功/失败/usage 缺失三态落库、写失败不阻断
新增单测(接入段) 缺口④⑤ ⑤ category 预检:未配置跳过/异常回退 enqueue;⑥ run-translate-category 专属:正常/字段缺失/AI 异常 enqueue/未配置降级
降级路径测试 NFR-3.8-01 双防线四测试点全量纳入(AI 未配置/故障/探测异常/重试耗尽,见风险与降级节)
集成点 跨模块 enqueue→claim→translate→落译文→事件→(storefront revalidate 由既有 translation-updated-revalidate 覆盖);接入段灰度演练脚本(步骤链 1-4)

风险与降级

风险卡

High 服务接入段门控(DEC-1 + C2)——顺序不可颠倒
C2 条款关闭前翻译扩量不得启动(PRD 7.5-2);D-dec1 仅阻塞服务接入段(生产配置、灰度开闸、扩量),不阻塞管道增强段 P-①~⑤ 全量。缓解:接入段步骤链顺序硬约束可审计(A3:条款生效时间戳 < 首次生产 AI 调用时间戳);接入段代码(category 预检对齐、专属单测)可先行开发但不上生产闸。
High R-encryption-fallback——明文降级+轮换无路径(探索新发现,代码缺陷非外部阻塞)
生产未设 TRANSLATION_ENCRYPTION_KEY 时 api_key 明文落库(upsert-translation-config.ts:52-56 无 NODE_ENV 守卫);密文无版本标记层,轮换即存量不可解。它是 AI 服务正式接入前的强制完成项(RK-02:接入前必须强制加密)。缓解:P-③ 守卫灰度→强制+enc:v2: 读旧写新轮换 runbook,建议排在接入段步骤链第 0 步。
Medium R-throughput——吞吐天花板(RK-09)
并发 3+5min cron+指数退避 2^n+MAX 5 构成吞吐上限;不阻塞开发,但阻塞「全量回填」运营动作的排期承诺。缓解:P-⑤ 四指标位随管道增强段落地;数值基线立项定(DEC-5);回填前按积压规模估算公式预估。
Low status API 与上游路由命名冲突
GET /admin/translations/status 可能与上游 @medusajs/translation 的 /admin/translations/* 命名冲突。缓解:实现期冲突时回退名 GET /admin/translation-jobs/status;以任务卡指定名为准并留冲突注记。

降级与跳过路径(NFR-3.8-01 强制范式——双防线跳过)

执行时序与分支见图 2。降级链路四条(转写自源 §5.3):

  • 防线一(入口预检):isAIConfigured(utils/translation-config.ts:49-66)——未配置(base_url/api_key/model 任一缺)→ 跳过 enqueue,主流程零感知;探测自身异常 → catch 返回 true(:63-65)→ 回退 enqueue 路径(C6 永不阻断);
  • 防线二(兜底终态化):retry worker 内再探测(:205-215)——未配置 → markSkippedNoConfig(job.id, SKIP_NO_CONFIG_REASON) 终态化(failed+no-config 前缀,logger.info 非告警——预期行为非告警);
  • AI 调用失败:enqueue 幂等重试(指数退避 2^n 分钟,MAX 5,:243-248 达上限告警);
  • storefront:译文缺失 → 原语言 fallback(C6 预期降级,collections/[handle]/page.tsx:42-44 同范式)。

NFR-3.8-01 范式测试点(必列,映射 PRD 验收要点「故障演练」):

  1. 未配置态:建商品/建分类 → 主流程成功、translation_job 0 新增或立即 markSkippedNoConfig;
  2. 故障态:mock AI 5xx/超时 → 主流程成功、任务进重试队列、指数退避生效;
  3. 探测异常态:mock getEffectiveConfig 抛错 → isAIConfigured 返回 true → enqueue 兜底(不静默丢);
  4. 重试耗尽:MAX 5 后 failed+告警日志(:243-248 断言)。

迁移与回滚

migration内容兼容性
translation-config 新 migrationALTER TABLE translation_config ADD COLUMN translatable_fields jsonb NULLNULL 默认→存量行为零变化
ai-call-log 模块 migrationCREATE TABLE ai_call_log(全列+索引)新表,无存量
轮换/明文迁移不用 schema migration——走 workflow(数据迁移与 schema 迁移分离,可重入幂等)—

pnpm db:reset / db:reset:demo 兼容:两 migration 均可空库重放;translatable_fields NULL→默认集回退使 seed 流程行为不变;ai_call_log 空表不参与 seed(seed 留空惯例一致)。

变更回滚路径
白名单参数化admin 清空 translatable_fields(或置 NULL)→ 回退内置默认集=现状行为;代码回滚无需数据回滚
加密守卫阶段 2 → 阶段 1:TRANSLATION_ENCRYPTION_REQUIRED 置 false(灰度开关即回滚开关)
密钥轮换读旧写新保证 v1 密文在轮换中始终可解;回滚=恢复旧钥环境变量+未轮换完的 v1 行原样保留(re-encrypt 幂等跳过已 v2 行,重跑即可续传)
状态 API / ai_call_log只读/只追加;停写旁路 recordAiCallLog(warn 旁路设计)即回退,无数据一致性风险
新能力总开关featureFlags.translation(medusa-config.ts:54-56)既有开关沿用;AI 配置缺省关闭即天然关闭(isAIConfigured 探测)

上线顺序(管道段内部建议序):P-③ 加密守卫最先(告警期先行积累观察)→ P-①②④ 并行 → P-⑤ 收口 → 接入段等 DEC-1/C2。全卡无源代码以外的仓库改动;本 TDD 为设计文档,不改任何源码/迁移/配置。

前置决策点

Blocker 二分表(阻塞什么 / 可并行做什么——禁止因阻塞点否定整条 TDD)

Blocker类型阻塞什么可并行做什么
D-dec1(翻译 AI 服务接入时点,PRD 2.5/8.1 C1) Blocker 待决策 管理层决策 仅阻塞服务接入段(生产配置、灰度开闸、扩量) 管道增强段 P-①~⑤ 全部五项零阻塞可立即开工;接入段代码(category 预检对齐、专属单测)可先行开发但不上生产闸
D-dec2(C2 条款时点与扩量节奏,PRD 2.5/8.1 C2) Blocker 待决策 管理层决策+法务 C2 条款关闭前翻译扩量不得启动(PRD 7.5-2 顺序不可颠倒) 条款内容起草(法务侧)与管道增强段并行;dev 环境内网 LLM 的工程验证不受限(生产数据不出域评估归 C2)
R-encryption-fallback(明文降级+轮换无路径) 接入前强制完成 代码缺陷(非外部阻塞) 不阻塞外部工作——但它是 AI 服务正式接入前的强制完成项(RK-02) 纳入管道增强段 P-③ 立即开工(守卫灰度→强制→轮换 runbook),建议排在接入段步骤链第 0 步
R-throughput(吞吐天花板,RK-09) 容量风险 容量风险 不阻塞开发;阻塞「全量回填」运营动作的排期承诺 指标位(P-⑤)随管道增强段落地;数值基线立项定(DEC-5);回填前按公式预估积压

内部依赖与下游消费

  • 内部依赖:无(本条为打样条目,无上游 TDD);
  • 下游消费:AI-3.3-02(三语检索以本管道译文为索引输入)、AI-3.5-03(场景翻译复用本管道+fillPrompt 扩展位)、AI-3.5-04(复核工作流挂本卡抽检点)、AI-3.9-03(P-④ 为其 P0 商用化动作「补 AI 调用专项审计(与翻译扩展同批)」的实现载体)。

开放数值决策

  • DEC-5 失败重试成功率与译文抽检合格率的口径基线与目标数值——基线立项定,本卡仅引用编号不内联臆造数值(外部决策点引用规范)。
  • DEC-5 吞吐/背压四指标(积压深度/端到端时延/重试成功率/理论吞吐)的目标数值与抽检采样率——同属 DEC-5,随实现纳入。
外部决策点引用规范

DEC-1(接入时点)、DEC-2(C2 条款时点与扩量节奏)、DEC-5(验收基线数值)——本卡仅引用编号,不内联臆造数值。

附录:溯源表

本文档由 docs/reports/ai-module-prd/tdd/AI-3.5-02-dynamic-content-translation.md(v1.0,2026-10-25)可读化重组生成。章节映射与处理方式如下:

本文档章节源文档章节处理方式
摘要文档头(元数据/depth_baseline/两态口径)+ §1 溯源与现状Summarized
关键概念§2 代码现状底座+§3 缺口与目标状态Summarized
方案设计 · 两段式总览§4.0 两段式总览Preserved
方案设计 · 设计红线§4.0 设计红线Preserved
方案设计 · 管道增强段 P-①~⑤§4.1(含字段表/JSON schema/响应 schema/轮换 SOP)Preserved
方案设计 · 服务接入段§4.2(步骤链+缺口④⑤收口+抽检挂接点)Preserved
数据模型 / API / Workflow§4.3 数据模型汇总+§4.4 API 契约汇总+§4.5 Workflow 设计+§4.1 P-④ 字段表Preserved
前端改动面与 i18n§4.6 前端改动面+§4.7 i18n key 规划Preserved
验收与测试§8.1 验收标准(A1-A9)+§8.2 测试策略与 L0 门禁Preserved
风险与降级(风险卡/降级链路/迁移回滚)§5.3 降级与跳过路径+§7 Blocker 二分表+§10 迁移与回滚Preserved
前置决策点§6 合规挂钩+§7 前置依赖与决策点Preserved
图 1 两段式开工条件§4.0 分段表+§4.2 步骤链+§9 子任务 DAGInferred(文字图转绘为流程图,门控关系一致)
图 2 翻译管道执行时序§2.1④ 触发链+§5.3 降级与跳过路径Inferred(双防线与重试链路转绘,分支条件一致)
图 3 translation_job 状态机§2.1③ 队列模型+constants 参数Inferred(按字段与常量转绘)
图 4 翻译域实体关系 ER 图§2.1①②③ schema+§4.1 P-④ 字段表+§4.3 汇总Inferred(按源 schema 字段转绘)
锚点复核约定

源 TDD 所有锚点在打样 run(run-64960599a217)中以 rg/sed 逐条复核;上游 findings 原始锚点与复核实际行号存在漂移处,均以「卡内锚点(复核:实际行号)」双写标注,内容实质均已验证成立。本页沿用源文档复核后的锚点表述(如 collect-product-input.ts 白名单判定实际 :36-60、markSkippedNoConfig 兜底调用实际 :205-215、secret-crypto 实际位于 src/utils/crypto/)。