Repository navigation
Commit 4564d6f
fix(spec/system): read the script-subtag Chinese spellings (zh-Hant-TW, zh-Hans-CN, …) as platform locale read aliases (#30681)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuV5cRafodM2YQHxeDkdiWJM)_
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
Fixes #30618
Clause-②: yes (widening)
Metadata-protocol: semantic
Skills-docs: none — `skills/**` carries no script-subtag or read-alias
claim, measured at `d8713f3f`: `git grep -i -E 'zh-Han[st]-[A-Za-z0-9]+'
-- skills/` 0 hits, `git grep -E
'PLATFORM_LOCALE_READ_ALIASES|platformLocaleTagOf|read alias|zh-Hant' --
skills/` 0 hits. The one docs page that states the read aliases,
`content/docs/protocol/kernel/i18n-standard.mdx` (its "Read aliases"
section), is fixed in this PR.
## 维护者速读
- 改了什么:平台语言表的读入别名加了五行:`zh-Hans-CN`、`zh-Hans-SG` 读作
`zh-Hans`,`zh-Hant-TW`、`zh-Hant-HK`、`zh-Hant-MO` 读作
`zh-Hant`。解析阶梯的“同表语言”一步对已注册的 key 也查这张表,所以拼成 `zh-Hant-TW` / `zh-Hans-CN`
的包 key 或可选代码,按它写明的文字归入繁体或简体。别名表仍是逐行列举的封闭清单,不按模式推断,也不按区域回退。文档页
`i18n-standard.mdx` 的 Read aliases 一节同步改正。
- 为什么改:浏览器和操作系统常发带文字子标签的语言代码(如
`zh-Hant-TW`)。原来这五个代码不在表里,同时有简体包和繁体包时,繁体读者拿到的是先注册的那个 zh-* 包;主干上
service-storage 的对象翻译包(`zh-CN` 排在 `zh-Hant` 前)就是这样把繁体读者答成简体。
- 风险与回滚:只改这五个代码(作请求或作已注册的 key
时)由哪个包来答,原来能匹配的请求仍能匹配,表外代码(`pt-PT`、`en-GB`、`zh-Hant-US`)照旧。Setup「默认语言」对这五个代码改用表里的名称(繁體中文
/ 简体中文),已存的繁体值改显示在繁體中文选项上;可接受的取值不变。回滚即还原本 PR。
- 先看:`packages/spec/src/system/platform-locales.ts`
的五行别名及其注释;`packages/core/src/fallbacks/fallbacks.test.ts`
的共享用例表(两个解析器、两种注册顺序,含 key
侧用例);`packages/spec/src/system/platform-locales.test.ts`
的封闭清单用例;`content/docs/protocol/kernel/i18n-standard.mdx` 的 Read aliases
一节。
- 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run
src/system/platform-locales.test.ts src/system/i18n-resolver.test.ts`,预期
2 个文件、576 例全绿。
- 需要你做的:无,席位落地。
## What changes
**`PLATFORM_LOCALE_READ_ALIASES`**
(`packages/spec/src/system/platform-locales.ts`) gains five rows, one
for the script-subtag spelling of each regional Chinese alias.
`platformLocaleTagOf`, FROM → TO:
| Code | FROM | TO |
|:--|:--|:--|
| `zh-Hans-CN` | `undefined` | `zh-Hans` |
| `zh-Hans-SG` | `undefined` | `zh-Hans` |
| `zh-Hant-TW` | `undefined` | `zh-Hant` |
| `zh-Hant-HK` | `undefined` | `zh-Hant` |
| `zh-Hant-MO` | `undefined` | `zh-Hant` |
Case is folded as for every row (`ZH-HANT-tw` reads as `zh-Hant`).
`PlatformLocaleReadAlias` gains the five literal keys. The list stays
closed: enumerated rows only, no pattern, no region fallback.
`zh-Hant-US`, `zh-Hans-US`, `pt-PT`, `de-AT`, `en-GB`, `es-419`,
`fr-CA`, `it` and `he` still answer `undefined`.
**What the ladder answers for a script-subtag request**
(`resolveLocaleAmong`, so `resolveBundleLocale` and core's
`resolveLocale` / `resolveOfferedLocale`), FROM → TO, measured by the
ablation below:
| Request | over `en, zh-CN, zh-Hant` | over `en, zh-Hant, zh-CN` | over
`en, zh, zh-Hant` |
|:--|:--|:--|:--|
| `zh-Hant-TW`, `zh-Hant-HK`, `zh-Hant-MO` | `zh-CN` → `zh-Hant` |
`zh-Hant` (unchanged) | `zh` → `zh-Hant` |
| `zh-Hans-CN`, `zh-Hans-SG` | `zh-CN` (unchanged) | `zh-Hant` → `zh-CN`
| `zh` (unchanged) |
**The key side.** The table step maps each registered key through the
table too (`i18n-resolver.ts` L573, unchanged), so a bundle key or
offered code spelled `zh-Hant-TW` / `zh-Hans-CN` now counts as the
script it names for every request the table reads as that script. FROM
measured on `main` `a2437b56` (whose `i18n-resolver.ts` and
`platform-locales.ts` are unchanged since the merge base `47c457b2`), TO
at this head; `resolveLocaleAmong`, `resolveBundleLocale` and core's
`resolveLocale` give the same answer in every row:
| Request | Keys, in registration order | FROM (`main`) | TO |
|:--|:--|:--|:--|
| `zh-TW` | `en, zh-CN, zh-Hant-TW` | `zh-CN` | `zh-Hant-TW` |
| `zh-TW` | `en, zh-Hant-TW, zh-CN` | `zh-Hant-TW` | `zh-Hant-TW`
(unchanged) |
| `zh-Hant` | `en, zh-CN, zh-Hant-TW` | `zh-CN` | `zh-Hant-TW` |
| `zh-Hant` | `en, zh-Hant-TW, zh-CN` | `zh-Hant-TW` | `zh-Hant-TW`
(unchanged) |
| `zh-CN` | `en, zh-Hant, zh-Hans-CN` | `zh-Hant` | `zh-Hans-CN` |
| `zh-CN` | `en, zh-Hans-CN, zh-Hant` | `zh-Hans-CN` | `zh-Hans-CN`
(unchanged) |
| `zh-Hant-TW` | `en, zh, zh-Hant-HK` | `zh` | `zh-Hant-HK` |
| `zh-Hant-TW` | `en, zh-Hant-HK, zh` | `zh` | `zh-Hant-HK` |
| `zh-Hant-TW` | `en, zh-CN` (one script registered) | `zh-CN` | `zh-CN`
(unchanged) |
No request that matched before stops matching. With no key of the
requested script, a script-subtag request still reaches the other
script's key, as before (last row).
**Docblocks** in `platform-locales.ts`: the read-alias block no longer
says "`zh-Hans-CN` is not listed"; it says the five rows exist, that the
list is closed, and that a script-subtag spelling without a row
(`zh-Hant-US`) answers nothing because rows are read one by one, never
as a pattern. The `platformLocaleTagOf` paragraph names `zh-Hans-CN` /
`zh-Hant-TW` beside `zh-CN` / `zh-TW`.
**Docs page** `content/docs/protocol/kernel/i18n-standard.mdx`, the
"Read aliases" section only (L166-207 at this head; nothing outside it
moves):
- The alias table lists the five rows. Its header is "Code", not "Stored
code", and the intro names the script-subtag spellings browsers and
operating systems send beside the codes stored values carry.
- "Any other code answers `undefined`" stays and is true again: the
section now says the list is closed and read row by row, never as a
pattern, so `zh-Hant-US` answers `undefined` too.
- The paragraph that said the ladder cannot tell the two Chinese scripts
apart and answers `zh` / `zh-CN` by key order (false since PR #30601
added the table step) is rewritten against the code. It names the
ladder's one function (`resolveLocaleAmong`, which `resolveBundleLocale`
and core's `resolveLocale` run) and its five steps, says the table step
takes the key spelled as the table tag or else the first key the table
maps to it, and says a two-script bundle answers `zh`, `zh-CN`,
`zh-Hans-CN` Simplified and `zh-TW`, `zh-Hant-TW` Traditional in any
registration order.
- A new paragraph names the two kinds of request that registration order
still decides: a code with no row (`zh-Hant-US` over `en, zh-CN,
zh-Hant` gets `zh-CN`, and `zh-Hant` when that key is registered first)
and a request whose table language has no key (`zh-TW` over `en, zh-CN`
gets `zh-CN`).
- Every example the section states is checked against the spec source at
this head by a scratch probe: 41 checks, all hold.
**Pins** (no new file; each extends the existing harness):
- `packages/spec/src/system/platform-locales.test.ts`: the five rows in
the alias table (`toEqual` over the whole map, now fifteen), a typed
index on `zh-Hant-TW`, a case-folded spelling of each row (`ZH-HANS-cn`,
`zh-hans-sg`, `ZH-HANT-tw`, `zh-hant-hk`, `Zh-Hant-Mo`). The closed-list
case drops `zh-Hans-CN` and `zh-Hant-TW`, keeps `pt-PT`, `de-AT`,
`en-GB`, `es-419`, `fr-CA`, `it`, `he`, and adds `zh-Hant-US` and
`zh-Hans-US`: a script-subtag code without a row still answers
`undefined`.
- `packages/spec/src/system/i18n-resolver.test.ts`: the
two-registration-order loop (`Simplified first` / `Traditional first`)
gains `zh-Hant-TW`, `zh-Hant-HK`, `zh-Hant-MO`, `ZH-HANT-tw` → `zh-Hant`
and `zh-Hans-CN`, `zh-Hans-SG`, `zh-hans-cn` → `zh-CN`. The "runs before
the base-language step" case gains `{ zh, zh-Hant }` for `zh-Hant-TW` →
`zh-Hant` and `zh-Hans-CN` → `zh`, and `zh-Hant-TW` over `en, zh,
zh-Hant-HK` → `zh-Hant-HK` in both orders. A new case, "reads a key
spelled with a script subtag as the script it names, in either
registration order", pins the key-side rows above and the one-script
control.
- `packages/core/src/fallbacks/fallbacks.test.ts`: PR #30601's shared
case list (run on both `resolveLocale` and `resolveBundleLocale`) gains
twelve request-side rows (each of the five codes in both registration
orders, `ZH-HANT-tw`, and `zh-Hant-TW` over `en, zh, zh-Hant`) and the
nine key-side rows of the table above (four requests in both
registration orders, plus the one-script control). A new case drives the
in-memory i18n service in both load orders for `zh-Hant-TW`,
`zh-Hant-HK`, `zh-Hans-CN`.
**Changeset** `.changeset/30618-script-subtag-read-aliases.md`:
`@objectstack/spec` minor. It states the rows, the request-side and
key-side moves (each moved key-side row with its before), the Setup
effect, and what stays. `@objectstack/core` is not moved (its diff is a
test file, outside its `files[]`).
## Verification
Head `d8713f3f32b323798a6416046bf4704ed6a9d82b`. Against the last fully
built head `e731b654` it adds two commits: `6a1d4627` (the docs section)
and `d8713f3f` (the key-side pins and the changeset sentence). That
delta is four files: the changeset, the docs page and two test files.
`platform-locales.ts` (blob `baefd871`) and `platform-locales.test.ts`
(blob `410cf43e`) are byte-identical to `e731b654`, so every non-test
source file the branch touches is unchanged since then. Against `main`
the branch is six files, +149 / −29 (merge base `47c457b2`). No full
build this round: spec was rebuilt by the ablation, then the dependency
closures of `@objectstack/core` and of `@objectstack/lint` /
`@objectstack/formula` were built (all exit 0).
- Tests, all exit 0 at this head: `@objectstack/spec` 817 files / 25799
tests + 1 todo (`platform-locales.test.ts` + `i18n-resolver.test.ts`: 2
/ 576); `@objectstack/core` 136 / 3130 (`fallbacks.test.ts` 151).
Carried from `e731b654`, code files byte-identical:
`@objectstack/service-settings` 69 / 1178,
`@objectstack/service-storage` 73 / 1353.
- Typecheck exit 0 for spec and core at this head; both compile their
tests through `check:test-typecheck` under `tsconfig.test.json`.
- eslint `--no-inline-config` on `platform-locales.ts`,
`platform-locales.test.ts`, `i18n-resolver.test.ts` and
`fallbacks.test.ts`: exit 0.
- `dispatch-gates --commands` at `d8713f3f`: 102 derived (79 at
`e731b654`; the 23 added came with the docs page, and `6a1d4627` already
derived these 102). 99 exit 0 on the first run. Three first answered
`PREREQUISITE NOT MET` (exit 3): `check:skill-examples` and
`check:lean-entry-closure` were re-run after building the client SDK and
objectql closures, exit 0; `check:dual-build-cjs-loads` needs every
package built, so its reading is carried from `e731b654` (exit 0), code
files byte-identical. `--ran` with the codes measured here: 102 derived,
101 run, 1 NOT-MEASURED (`check:dual-build-cjs-loads`), 0 UNRUN; with
the carried reading: 102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN.
- The derivation at `d8713f3f` warned `STALE TREE` (at least 7 commits
behind `origin/main`, and 8 files the derivation reads changed); after a
fetch, `origin/main` `badf24df` is 12 commits ahead. Re-derived from
`origin/main` `badf24df` with this branch merged in (a scratch merge in
a separate worktree, clean, never committed): the same 102 commands.
- `pnpm --filter @objectstack/spec check:generated`: exit 0, all 14
artifacts up to date.
- `check:dev-prereqs` (local-only family; it needs every package built):
carried from `e731b654` (exit 0), code files byte-identical. At this
head it answers exit 1, naming the unbuilt `dist/` folders; its
`--self-test`, which is derived, is exit 0 here.
- CI: read from this PR's check-runs. At this head, when read: 65
check-runs, 57 success, 8 skipped, 0 failed.
<details><summary>Derived gates at <code>d8713f3f</code> (102, each exit
code captured before any pipe)</summary>
| Command | Exit |
|:--|:--|
| `node scripts/check-adr-0087-registration.mjs --base origin/main` | 0
|
| `node scripts/check-adr-0087-registration.mjs --self-test` | 0 |
| `node scripts/check-adr-links.mjs` | 0 |
| `node scripts/check-adr-links.mjs --self-test` | 0 |
| `node scripts/check-changeset-no-major.mjs --base origin/main` | 0 |
| `node scripts/check-closing-keyword-parity.mjs` | 0 |
| `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 |
| `node scripts/check-comment-mask-adoption.mjs` | 0 |
| `node scripts/check-comment-mask-adoption.mjs --self-test` | 0 |
| `node scripts/check-comment-mask-corpus.mjs` | 0 |
| `node scripts/check-dev-prereqs.mjs --self-test` | 0 |
| `node scripts/check-doc-route-spelling.mjs --advisory` | 0 |
| `node scripts/check-doc-route-spelling.mjs --self-test` | 0 |
| `node scripts/check-docs-section-name.mjs` | 0 |
| `node scripts/check-docs-section-name.mjs --self-test` | 0 |
| `node scripts/check-dts-emitted.mjs --self-test` | 0 |
| `node scripts/check-empty-changeset.mjs --base origin/main` | 0 |
| `node scripts/check-empty-changeset.mjs --self-test` | 0 |
| `node scripts/check-issue-citations.mjs` | 0 |
| `node scripts/check-plugin-teardown-shape.mjs` | 0 |
| `node scripts/check-plugin-teardown-shape.mjs --self-test` | 0 |
| `node scripts/check-section-landing-index.mjs` | 0 |
| `node scripts/check-section-landing-index.mjs --self-test` | 0 |
| `node scripts/ci/assert-turbo-remote-cache.mjs --self-test` | 0 |
| `node scripts/ci/plan-test-core-shards.mjs --self-test` | 0 |
| `node scripts/ci/prune-turbo-cache.mjs --self-test` | 0 |
| `node scripts/docs-audit/check-affected-docs.mjs` | 0 |
| `node scripts/docs-audit/check-drift-comment.mjs` | 0 |
| `node scripts/pm/release-rehearsal-clone.mjs --self-test` | 0 |
| `node scripts/release-pending-publish.mjs --self-test` | 0 |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
0 |
| `pnpm --filter @objectstack/lint run check:doc-security-posture` | 0 |
| `pnpm --filter @objectstack/spec run check:api-surface` | 0 |
| `pnpm --filter @objectstack/spec run check:authorable-surface` | 0 |
| `pnpm --filter @objectstack/spec run check:browser-reachable-entries`
| 0 |
| `pnpm --filter @objectstack/spec run check:docs` | 0 |
| `pnpm --filter @objectstack/spec run check:dual-source-exports` | 0 |
| `pnpm --filter @objectstack/spec run check:duration-unit-keys` | 0 |
| `pnpm --filter @objectstack/spec run check:empty-state` | 0 |
| `pnpm --filter @objectstack/spec run check:entry-nameability` | 0 |
| `pnpm --filter @objectstack/spec run check:export-origins` | 0 |
| `pnpm --filter @objectstack/spec run check:exported-any` | 0 |
| `pnpm --filter @objectstack/spec run check:liveness` | 0 |
| `pnpm --filter @objectstack/spec run check:llms-txt` | 0 |
| `pnpm --filter @objectstack/spec run check:skill-examples` | 0 (re-run
after building the client SDK closure; first answered 3, PREREQUISITE
NOT MET) |
| `pnpm --filter @objectstack/spec run check:skill-refs` | 0 |
| `pnpm --filter @objectstack/spec run check:strictness-ledger` | 0 |
| `pnpm --filter @objectstack/spec run check:variant-docs` | 0 |
| `pnpm --filter @objectstack/spec run check:yaml-examples` | 0 |
| `pnpm check:changeset-gate-self-tests` | 0 |
| `pnpm check:ci-filter-parity` | 0 |
| `pnpm check:closed-ceremony-text` | 0 |
| `pnpm check:corpus-claim-drift` | 0 |
| `pnpm check:cross-package-test-inputs` | 0 |
| `pnpm check:dispatcher-error-vocabulary` | 0 |
| `pnpm check:doc-anchors` | 0 |
| `pnpm check:doc-authoring` | 0 |
| `pnpm check:doc-frontmatter` | 0 |
| `pnpm check:docs-audit-scope` | 0 |
| `pnpm check:docs-redirects` | 0 |
| `pnpm check:docs-single-h1` | 0 |
| `pnpm check:docs-spec-enumerations` | 0 |
| `pnpm check:docs-transcript-drift` | 0 |
| `pnpm check:driver-memory-census` | 0 |
| `pnpm check:dts-closure` | 0 |
| `pnpm check:dual-build-cjs-loads` | 3 here (PREREQUISITE NOT MET:
unbuilt packages, no full build this round); 0 carried from e731b65,
code files byte-identical |
| `pnpm check:engine-double-contract` | 0 |
| `pnpm check:gitlink-declared` | 0 |
| `pnpm check:issue-citations` | 0 |
| `pnpm check:kernel-hook-pairs` | 0 |
| `pnpm check:keyed-text-bounds` | 0 |
| `pnpm check:lean-entry-closure` | 0 (re-run after building objectql's
closure; first answered 3, PREREQUISITE NOT MET) |
| `pnpm check:logger-receiver-detach` | 0 |
| `pnpm check:merge-driver` | 0 |
| `pnpm check:nul-bytes` | 0 |
| `pnpm check:objectql-double-limit` | 0 |
| `pnpm check:org-identifier` | 0 |
| `pnpm check:page-declaration-shape` | 0 |
| `pnpm check:platform-object-tenancy-census` | 0 |
| `pnpm check:pm-prior-rulings` | 0 |
| `pnpm check:published-files` | 0 |
| `pnpm check:published-readme-links` | 0 |
| `pnpm check:query-options-erasure` | 0 |
| `pnpm check:react-page-adapter-contract` | 0 |
| `pnpm check:refd-timer-probe` | 0 |
| `pnpm check:registry-log-declared` | 0 |
| `pnpm check:rest-log-spy-declared` | 0 |
| `pnpm check:role-word` | 0 |
| `pnpm check:skill-identifier-liveness` | 0 |
| `pnpm check:slot-lookup` | 0 |
| `pnpm check:sourcemap-no-sources-content` | 0 |
| `pnpm check:spec-docblock-symbol-anchors` | 0 |
| `pnpm check:spec-parsed-alias` | 0 |
| `pnpm check:system-context-census` | 0 |
| `pnpm check:test-source-alias` | 0 |
| `pnpm check:tier-file-adoption` | 0 |
| `pnpm check:type-check-coverage` | 0 |
| `pnpm check:type-check-debt` | 0 |
| `pnpm check:undeclared-dep-imports` | 0 |
| `pnpm check:vendor-version-stamps` | 0 |
| `pnpm check:watch-hint-literal` | 0 |
| `pnpm check:where-matcher` | 0 |
</details>
<details><summary>Ablation: drop the five rows (run at
<code>d8713f3f</code>)</summary>
- Mutation through `scripts/ablation-replace.mjs --delete` on the five
rows and their comment: anchor 1 → 0, blob `baefd871` → `be1e0adf`; on
disk the rows count 0. Spec rebuilt (`VERDICT command-exit 0`);
`ablation-dist-preflight --absent` for `"zh-Hant-TW": "zh-Hant"` and
`"zh-Hans-CN": "zh-Hans"`: exit 0 for both.
- spec `platform-locales.test.ts` + `i18n-resolver.test.ts`: **21
failed** / 555 passed (576). Red: the five rows, their five case-folded
spellings, the whole-map and typed-key cases, the two-order loop entries
(`zh-Hant-TW` / `zh-Hant-HK` / `zh-Hant-MO` / `ZH-HANT-tw` under
"Simplified first", `zh-Hans-CN` / `zh-Hans-SG` / `zh-hans-cn` under
"Traditional first"), the `{ zh, zh-Hant }` case, and the new key-side
case (first failing assertion: `zh-TW` over `en, zh-CN, zh-Hant-TW`
received `zh-CN`). The two `zh-Hant-HK` assertions added to the `{ zh,
zh-Hant }` case come after an assertion that is already red there, so
vitest stops before them; core's list below fails those rows one by one.
- core `fallbacks.test.ts`: **26 failed** / 125 passed (151). Red: on
each of `resolveLocale` and `resolveBundleLocale`, the seven
request-side rows (each code in the order where the other script is
registered first, `ZH-HANT-tw`, and `zh-Hant-TW` over `en, zh, zh-Hant`)
and five key-side rows (`zh-TW` and `zh-Hant` over `en, zh-CN,
zh-Hant-TW`, `zh-CN` over `en, zh-Hant, zh-Hans-CN`, `zh-Hant-TW` over
`en, zh, zh-Hant-HK` in both orders); plus the in-memory service case in
both load orders. Green, as the FROM column says: the key-side rows in
the order where the script-subtag key is registered first, and the
one-script control.
- A probe of the nine key-side rows through spec's `resolveLocaleAmong`
and `resolveBundleLocale` (source) and core's `resolveLocale` (core
source over spec's built `dist/`) answers exactly the FROM column on the
mutated tree, and exactly the TO column after the restore.
- Restore: `git checkout HEAD --
packages/spec/src/system/platform-locales.ts`; blob after restore
`baefd8712cec92d2a22aac2a8f3fd909a62635a5` equals the HEAD blob, `git
diff HEAD` empty, `git status --porcelain` empty. Rebuilt (exit 0); both
markers present (preflight exit 0); spec 576 passed, core 151 passed.
- The consumer suites were not read in this run: with only spec's
`dist/` on disk, service-settings and service-storage failed to resolve
workspace package entries (`@objectstack/metadata-core`) in both legs
alike, a setup failure and not a reading. Their reading is carried from
the round-one ablation at `eadbe6e8` (same `platform-locales.ts` blob
`baefd871`): service-settings 1178 and service-storage 1353 green with
and without the rows, so no consumer test pins these answers.
</details>
<details><summary>Consumer probe (each direct caller, 14 codes, with and
without the rows; carried from <code>e731b654</code>, code files
byte-identical)</summary>
- `platformLocaleTagOf`: the five codes and `ZH-HANT-tw` move
`undefined` → table tag; `zh-TW`, `zh-CN`, `zh-Hant`, `zh-Hans`
unchanged; `zh-Hant-US`, `zh-Hans-US`, `pt-PT`, `fr-CA` stay
`undefined`.
- service-settings `localeSelfName`: the five codes move from the
runtime display name to the table label (see Acceptance notes); the
other nine unchanged.
- service-settings option source, four scenarios: `admits` identical for
all 14 codes; options move only where a stored or offered code is one of
the five.
- service-settings `notOfferedCopy`: unchanged for all 14.
- service-storage `storageTestCopyLocaleOf`: unchanged for all 14.
</details>
## Acceptance notes
- **The docs page is fixed in this PR (this round).** The seat widened
the file surface to the "Read aliases" section of
`content/docs/protocol/kernel/i18n-standard.mdx` (#30618 comment
6671045459), and commit `6a1d4627` edits that section only: the five
rows, the closed-list sentence made true, and the ladder paragraphs
rewritten against the code (see What changes). So the `Skills-docs:`
line is `none`, and the follow-up the earlier body owed is closed.
- **Review S1 adopted (this round).** The contract review (comment
6671200273, PASS at `e731b654`) asked that the key-side move be pinned
and stated. Commit `d8713f3f` pins it in core's shared list and in
spec's table-step cases, both registration orders, with the one-script
control, and adds the changeset sentence. FROM was measured on `main`
first; it agrees with the review's three rows (`zh-TW` over `en, zh-CN,
zh-Hant-TW`: `zh-CN` → `zh-Hant-TW`; `zh-Hant-TW` over `en, zh,
zh-Hant-HK`: `zh` → `zh-Hant-HK`; control `zh-Hant-TW` over `en, zh-CN`:
`zh-CN`, unchanged).
- **The closed list, measured, not re-decided.** A script-subtag code
with any other region (`zh-Hant-US`, `zh-Hans-US`) has no row, so it
keeps the old path: over service-storage's shipped bundle it reaches
`zh-CN`, the first `zh-*` key, for either script. That is the decision
of record on this card (enumerated rows only).
- **Consumers measured, none edited.** Every direct caller of
`platformLocaleTagOf` / `PLATFORM_LOCALE_READ_ALIASES` (`git grep`,
three non-test call sites; the map itself is read only inside
`platform-locales.ts`):
- `packages/spec/src/system/i18n-resolver.ts` L569/L573
(`resolveLocaleAmong` table step): moves, as the tables above, on the
request side (L569) and on the key side (L573).
-
`packages/services/service-settings/src/localization-locale-options.ts`
L67 (`localeSelfName`): **moves.** The five codes are named from the
table (`zh-Hant-TW` 中文(繁體,台灣) → 繁體中文, `zh-Hant-HK` 中文(繁體字,中國香港特別行政區) →
繁體中文, `zh-Hans-CN` 中文(简体,中国) → 简体中文, likewise `zh-Hans-SG`,
`zh-Hant-MO`). Through `resolveOfferedLocale`, a stored `zh-Hant-TW`
over offered `en, zh-CN, zh-Hant` used to take the place of the 简体中文
option (`zh-Hant-TW=简体中文`) and now sits on 繁體中文 (`zh-CN=简体中文`,
`zh-Hant-TW=繁體中文`); a stored `zh-Hans-CN` over `en, zh-Hant, zh-CN`
moved the same way. The accepted set (`admits`) is unchanged for all 14
probed codes in 4 scenarios, and `notOfferedCopy` is unchanged.
- `packages/services/service-storage/src/storage-test-copy.ts` L152
(`storageTestCopyLocaleOf`): **does not move.** Its subtag truncation
already reached the same tag (`zh-Hant-TW` → `zh-Hant`), for all 14
probed codes.
- Ladder consumer reached after the `main` merge: service-storage's
object translations (`zh-CN` registered before `zh-Hant`, from #30649)
now serve a `zh-Hant-TW` / `zh-Hant-HK` / `zh-Hant-MO` reader the
`zh-Hant` objects; the row-less control `zh-Hant-US` still reaches
`zh-CN`.
- Consumer suites green with and without the rows (service-settings 69
files / 1178 tests, service-storage 73 / 1353): no consumer test pins
these answers.
- **Declarations, measured.** `Clause-②: yes (widening)`: the published
map and `PlatformLocaleReadAlias` gain five keys, nothing removed or
narrowed (`check:api-surface` green: the snapshot records names and
kinds); the key-side move is a widening too. `Metadata-protocol:
semantic`: no key or value is added to what an author writes, but a
bundle key or translation item an author keys `zh-Hant` (or `zh-CN` /
`zh-Hans`) now serves `zh-Hant-*` (or `zh-Hans-*`) readers it did not
serve over a two-script bundle, and a key an author spells `zh-Hant-TW`
is now read as `zh-Hant`. Level `minor` on `@objectstack/spec`, the one
package the diff moves.
- **The first round-one ablation run was a no-op on its `dist/` half**,
and is not a reading reported here. Its mutation landed, but the spec
build in the mutated leg never got the shared lock (`VERDICT
queue-timeout (exit 99)`), so `dist/` still carried the rows. Every
later leg aborts unless the build verdict is `command-exit 0` and the
dist preflight passes; both held in this round's run.
- **Docblocks left as they are, still true:** `resolveLocaleAmong`'s
step 3 (`i18n-resolver.ts`) and core's `resolveLocale` /
`resolveOfferedLocale` docblocks give `zh-TW` / `zh-HK` examples, not an
exhaustive list. The unreleased
`.changeset/30001-platform-locale-table.md` says the map is "typed on
its ten literal keys", which is true of that change; this changeset
states the five added.
- No labels sent: the dispatch named none, and `skip-changeset` does not
apply (the diff publishes `@objectstack/spec`).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P
<!-- signed: domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ab44ec9 commit 4564d6f
6 files changed
Lines changed: 149 additions & 29 deletions
File tree
- .changeset
- content/docs/protocol/kernel
- packages
- core/src/fallbacks
- spec/src/system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
| 166 | + | |
| 167 | + | |
167 | 168 | | |
168 | | - | |
| 169 | + | |
169 | 170 | | |
170 | | - | |
| 171 | + | |
171 | 172 | | |
172 | | - | |
173 | | - | |
| 173 | + | |
| 174 | + | |
174 | 175 | | |
175 | 176 | | |
176 | 177 | | |
177 | 178 | | |
178 | 179 | | |
179 | 180 | | |
180 | | - | |
181 | | - | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
182 | 185 | | |
183 | 186 | | |
184 | 187 | | |
185 | 188 | | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | | - | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
193 | 208 | | |
194 | 209 | | |
195 | 210 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
315 | 315 | | |
316 | 316 | | |
317 | 317 | | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
318 | 342 | | |
319 | 343 | | |
320 | 344 | | |
| |||
344 | 368 | | |
345 | 369 | | |
346 | 370 | | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
347 | 384 | | |
348 | 385 | | |
349 | 386 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1048 | 1048 | | |
1049 | 1049 | | |
1050 | 1050 | | |
1051 | | - | |
1052 | | - | |
1053 | | - | |
1054 | | - | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
1055 | 1056 | | |
1056 | 1057 | | |
1057 | 1058 | | |
| |||
1060 | 1061 | | |
1061 | 1062 | | |
1062 | 1063 | | |
1063 | | - | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
1064 | 1068 | | |
1065 | 1069 | | |
1066 | 1070 | | |
1067 | | - | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
1068 | 1075 | | |
1069 | 1076 | | |
1070 | 1077 | | |
| |||
1079 | 1086 | | |
1080 | 1087 | | |
1081 | 1088 | | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
1082 | 1107 | | |
1083 | 1108 | | |
1084 | 1109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
64 | 70 | | |
65 | 71 | | |
66 | 72 | | |
| |||
72 | 78 | | |
73 | 79 | | |
74 | 80 | | |
75 | | - | |
| 81 | + | |
76 | 82 | | |
77 | 83 | | |
78 | 84 | | |
79 | | - | |
| 85 | + | |
80 | 86 | | |
81 | 87 | | |
| 88 | + | |
| 89 | + | |
82 | 90 | | |
83 | 91 | | |
84 | 92 | | |
| |||
113 | 121 | | |
114 | 122 | | |
115 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
116 | 129 | | |
117 | 130 | | |
118 | 131 | | |
119 | 132 | | |
120 | | - | |
121 | | - | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
122 | 137 | | |
123 | 138 | | |
124 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
62 | | - | |
63 | | - | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
64 | 69 | | |
65 | 70 | | |
66 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
67 | 74 | | |
68 | 75 | | |
69 | 76 | | |
| |||
73 | 80 | | |
74 | 81 | | |
75 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
76 | 89 | | |
77 | 90 | | |
78 | 91 | | |
| |||
109 | 122 | | |
110 | 123 | | |
111 | 124 | | |
112 | | - | |
113 | | - | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
114 | 128 | | |
115 | 129 | | |
116 | 130 | | |
| |||
0 commit comments