fix(spec)!: composeStacks 的 i18n 退出 last-wins,与其余非数组顶层键对齐 (#5051) - #6240
Merged
Conversation
#5005 把 `composeStacks` 的非数组顶层键统一成「同值放行 / 冲突报错带处方」, 维护者 2026-08-04 裁决点名否掉 last-wins(先声明的栈被后组合者无声覆盖)与 deep-merge(造出两位作者都没写过的第三种值)。`i18n` 是唯一逃过那一轮的键 —— #5005 的主题是**被丢掉**的键,而 `i18n` 本来就有一个能工作(只是无声)的策略。 于是它成了整个顶层键面上仅剩的一个 last-wins。2026-08-06 裁决取 A(对齐), 2026-08-07 窗口由 v18 拉入 v17。 实施:`i18n` 的处置从 `'i18n'` 改为 `'single'`,`composeStacks` 里那段专用的 last-wins 循环删除(该键随之落到既有的单值路径 `composeSingleValue`), `ComposeDisposition` 联合类型去掉 `'i18n'` 分支 —— 处置表仍是 `Record< keyof ObjectStackDefinition, ComposeDisposition >`,总覆盖不变。 `ComposeStacksOptionsSchema` 按 #5005 裁决不加旋钮。 为什么本地化配置不能择一:一个栈的 `translations` 语言包是照着它自己声明的 `supportedLocales` 写的。让附加包的 `i18n` 胜出,等于让基础栈的语言包去寻址一组 组合后的应用不再承认的 locale,运行时只表现为「查不到翻译」。单值冲突的错误信息 补上了这条 i18n 专属的危害,处方(改一致 / 只在应当拥有它的栈里声明)不变。 测试:新增 `compose-stacks-i18n-merge.test.ts`(14 例)—— 冲突报错并点名两个栈、 顺序无关、三栈报首个分歧对、无 manifest 时用 `stack #N`、strict `defineStack` 路径同样报错、部分键 vs 部分键**不**深合并;控制面为单方声明/同值放行/显式 `undefined` 视同未声明/零 warn/单栈短路。#5005 钉现状的控制用例与 `compose-stacks.test.ts` 的 last-wins 用例按裁决反转。 反向验证(方向事先声明为「红」):把删掉的 last-wins 循环装回去,9 条冲突用例 全红(其余 71 条控制用例保持绿,因为 last-wins 与单值语义在「没有分歧」时本就 一致);实测丢失侧 —— 装回后 `composeStacks([a,b]).i18n` 得 `{"defaultLocale":"zh-CN",…}`,栈 #0 的 `en` 声明不在结果里。 仓内实测:`examples/app-crm`(`['en','zh-CN']`)与 `examples/app-todo` (`['en','zh-CN','ja-JP']`)正是会冲突的一对,组合文档据此补了一段说明。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
qq9340100
marked this pull request as ready for review
August 7, 2026 12:00
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 #5051
背景与裁决口径
#5005 把
composeStacks的非数组顶层键统一成「同值放行 / 冲突报错带处方」,维护者 2026-08-04 裁决点名否掉了两种形状:⛔ last-wins(先声明的栈被后组合者无声覆盖)、⛔ deep-merge(造出两位作者都没写过的第三种值)。i18n是唯一逃过那一轮的键 —— #5005 的主题是被丢掉的键,而i18n本来就有一个能工作(只是无声)的策略,所以被显式留在范围外。于是它成了整个顶层键面上仅剩的一个 last-wins。ComposeStacksOptionsSchema不加旋钮。target:v18→target:v17(已决定的协议变更一律落 v17)。本 PR 按裁决原文与既有单值键(
api/server/runtimeModule)的实现执行,没有第三种语义:两栈各带部分i18n子键、彼此不同,判为冲突报错,而不是把两侧并起来(那正是被否掉的 deep-merge)。改了什么
packages/spec/src/stack.zod.ts:COMPOSE_KEY_DISPOSITIONS.i18n由'i18n'改为'single',ComposeDisposition联合类型去掉'i18n'分支;composeStacks里那段专用的 last-wins 循环删除 ——i18n随之落到既有的单值路径composeSingleValue,步骤编号顺延(6 步变 5 步);Record< keyof ObjectStackDefinition, ComposeDisposition >,总覆盖不变(新增顶层键不声明合成语义仍然tsc不过);translations语言包会去寻址组合后不再承认的 locale),点名冲突键 / 两个来源栈 / 两条出路的处方结构不变。行为激活面(FROM → TO)
i18ni18ni18ni18n与两个来源栈(无 manifest 时用stack #N)被静默丢弃的那一侧不再消失 —— 它以一条点名两个来源栈的错误出现,由作者决定保留哪一个。changeset 按此定级
major并写明迁移(改一致 / 只在应当拥有它的栈里声明)。仓内实测
examples/app-crm(supportedLocales: ['en','zh-CN'])与examples/app-todo(['en','zh-CN','ja-JP'])正是会冲突的一对:defaultLocale一致,locale 集合不一致。content/docs/getting-started/examples.mdx的组合示例用的就是这两个 app,因此补了一段说明(单值键既不覆盖也不合并 + 这一对需要先对齐)。除此之外无运行时调用点组合带i18n的多个栈。测试
新增
packages/spec/src/compose-stacks-i18n-merge.test.ts(14 例):stack #N、strictdefineStack路径同样报错、部分键 vs 部分键不深合并、examples 实测形状(同defaultLocale不同 locale 集合)。i18n: undefined视同未声明、无人声明则结果里没有该键、零console.warn(证明它命中了已声明规则而不是默认路径)、单栈短路不受影响。按裁决反转的既有用例:
compose-stacks-key-loss.test.ts里 #5005 钉现状的 i18n 控制用例(该 describe 改名,i18n 覆盖迁到新文件),compose-stacks.test.ts的should use last i18n config (last-wins)改为断言冲突报错并补一条同值放行。反向验证(方向事先声明为「红」)
把删掉的 last-wins 循环装回去:9 条冲突用例全红,其余 71 条保持绿 —— 后者是控制而非证据,因为 last-wins 与单值语义在「没有分歧」时本就一致。实测丢失侧:装回后
composeStacks([a, b]).i18n得{"defaultLocale":"zh-CN","supportedLocales":["zh-CN"]},栈 #0 的en声明不在结果里。脚手架已移除(git grep零残留)。命令与凭据
check:generated只在api-surface/一项报 stale,原因是本 worktree 从未 build 过dist(check:api-surface直报Could not resolve module symbol … Is the package built?);该产物只记导出名("composeStacks (function)"),本 PR 零导出增删改,故判定不涉及、不手改。以 CI 的 TypeScript Type Check 逐 job 结论为准。边界
⛔ 只动
i18n这一个键的合成路径;⛔ 未碰content/docs/releases/;⛔ 未碰.zod.ts的别名区(#6083 在飞);⛔ComposeStacksOptionsSchema未加旋钮。Generated by Claude Code