Skip to content

Commit 9bf5e67

Browse files
fix(platform-objects): translate the zh-CN object labels, options and help that shipped as English copies (#20490)
Fixes #20462 Clause-②: no ## What changed A zh-CN console showed English on Setup surfaces: the Invite user dialog listed the membership roles as 所有者 / 管理员 / **Delegated Admin** / 成员, and a team record read **MEMBER COUNT**. The keys were present in `packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts`, but their values were byte copies of the `en` source, as `os i18n extract --fill=default` seeds them. - **320 copied leaves translated** in the zh-CN objects bundle. Only leaf values changed; the structure is untouched. The two reproduced strings now read 受托管理员 (on both `sys_member` and `sys_invitation`) and 成员数. - **42 leaves kept in English by design**, each declared with its reason in a decision ledger, `objects-zh-cn-echo-decisions.test.ts`. The list is below. - **The provenance companion** `zh-CN.source-hashes.generated.ts` was regenerated by `pnpm i18n:extract`, never by hand. It records a leaf only while the leaf is a byte copy of its source, so 320 rows dropped and 42 remain. The 42 are set-equal to the declared echoes. - **Pin:** the reproduced leaves `sys_member.fields.role.options.delegated_admin`, `sys_invitation.fields.role.options.delegated_admin` and `sys_team.fields.member_count.label` are pinned as translated under zh-CN. The role options must read one word per role on both objects. - `ja-JP` and `es-ES` are not touched. They carry the same copy class; their counts are under *Out of scope*. **The coverage-ratchet half is out of scope, per triage (5873680175).** Teaching `i18n-coverage-baseline` to count a copied source string as untranslated widens an existing gate. If the maintainer wants that ratchet, it is a separate gate card. The new ledger deliberately does not assert that no other zh-CN leaf is a copy, for the same reason. ## Copy count, before and after, by key class A zh-CN string leaf counts as a copy when it is byte-equal to the `en` leaf at the same path, over every leaf of `en.objects.generated.ts` / `zh-CN.objects.generated.ts`. Both bundles have 1529 leaves. | key class | before (b810ddb) | after | |---|---:|---:| | `label` (object, field, view, action param) | 196 | 27 | | `options.*` | 20 | 9 | | `pluralLabel` | 11 | 0 | | `help` | 114 | 0 | | `description` | 11 | 0 | | `placeholder` | 4 | 4 | | `emptyState.title` / `.message` | 4 | 0 | | result-dialog field (`client.client_id`, `client.client_secret`) | 2 | 2 | | **total** | **362** | **42** | | `label` + `options.*` | 216 | 36 | The card reads 338 in total, 192 of them `label` / `options.*`, at 3cf6449. The zh-CN and en object bundles are byte-identical between 3cf6449 and b810ddb, and this walk reads 362 / 216 at 3cf6449 too. So the difference of 24 comes from the counting method, and the card does not state its method. `help` / `description` / `placeholder` / `emptyState` are decided as the same class as `label`. The extractor files each of them as an `ExpectedEntry` under the same `source` kind as its element's label (`field`, `object`, `view`, `action`; `packages/cli/src/utils/i18n-extract.ts`), and the coverage detector consumes them identically. So they were decided as the same class: `help`, `description` and `emptyState` were translated, and all four placeholders are declared echoes, because each shows a value the admin types (see the list below). ## Kept in English by design (42), with reasons | leaves | value(s) | reason | |---|---|---| | `sys_account._actions.link_social.params.provider.options.{google, github, microsoft, apple, facebook, gitlab, discord}` (7) | Google, GitHub, Microsoft, Apple, Facebook, GitLab, Discord | Sign-in provider brands, shown on the link button as the name a user recognises. The bundle renders no brand name: the authored SSO empty state keeps Okta, Entra and Auth0 verbatim. | | `fields.id.label` on `sys_oauth_application`, `sys_oauth_access_token`, `sys_oauth_refresh_token`, `sys_oauth_consent`, `sys_oauth_resource`, `sys_oauth_client_resource`, `sys_oauth_client_assertion`, `sys_sso_provider`, the eight `sys_scim_*` objects, `sys_email_template`, `sys_metadata`, `sys_metadata_history`, `sys_view_definition`, `sys_metadata_audit`, `sys_secret`, `sys_setting_audit` (23) | ID | The bundle keeps the initialism verbatim in every label that carries it (用户 ID, 团队 ID, 客户端 ID). A bare `ID` has no noun to render, and adding one would invent content. | | `sys_oauth_application.fields.jwks.label`, `.jwks_uri.label` (2) | JWKS, JWKS URI | Protocol names the bundle keeps verbatim: `sys_jwks` is authored 签名密钥 (JWKS), and URIs stay URI (重定向 URI). Their `help` leaves are translated. | | `sys_oauth_application._actions.create_oauth_application.resultDialog.fields.client.client_id` / `.client_secret` (2) | Client ID, Client Secret | The credential names an admin copies out of the one-time dialog. The same dialog family authored its zh-CN text with the names verbatim (我已保存 Client Secret, 新的 Client Secret). | | `sys_sso_provider._actions.register_saml_provider.params.entryPoint.label` (1) | IdP SSO URL | Nothing but protocol initialisms the bundle keeps verbatim. The sibling SAML params are authored IdP 实体 ID and IdP 签名证书. | | `sys_sso_provider._actions.register_sso_provider.params.{scopes, mapEmail, mapName}.placeholder` (3) | openid email profile, email, name | A placeholder shows the literal value the admin types: OIDC scope and claim names the IdP matches byte for byte. The authored help on the same params keeps them verbatim (默认为 “email”). | | `sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder` (1) | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | The SAML NameID format URN the admin types: an identifier, not prose. | | `sys_email.fields.message_id.label` (1) | Message-ID | The RFC 5322 header name. The authored help on the same field keeps it (传输层分配的 RFC-5322 Message-ID). | | `sys_setting_audit.fields.source.options.{ui, api}` (2) | UI, API | Initialisms the bundle keeps wherever they appear (API 密钥, 在 UI 中暴露). | **Where the declaration lives.** Neither the extract config nor `os i18n extract` / `os i18n check` has a keep-source mechanism: no allowlist, no flag, no provenance mark. `--filter` only narrows emission. The existing mechanism that declares a leaf "English by design" is this package's echo-decision ledger. The shape is `type Verdict = 'translate' | 'echo'`, with a reason on every row and a departure reason required for every `echo` (`undeclaredEchoes`). It lives in `packages/platform-objects/src/apps/translations/*-echo-decisions.test.ts`, one file per family. `report-dataset-panel-echo-decisions.test.ts` is the first: it has `Verdict`, a reason per row and the per-locale departure check written inline. The named `undeclaredEchoes` predicate arrives in the later rounds, first in `object-field-editor-panel-echo-decisions.test.ts`. `objects-zh-cn-echo-decisions.test.ts` is that shape applied to the objects bundle. No new mechanism was added. ## Terminology The terms follow the zh-CN leaves that were already authored, so one term is not translated two ways: - `delegated_admin` is 受托管理员. That is the console's own word for this role value (objectui `organization.roles.delegatedAdmin`). The other three roles already read the same in both places (所有者 / 管理员 / 成员). The alternative is 委派管理员, the in-repo word for the ADR-0090 concept (委派管理范围). One line changes it if the maintainer prefers it. - SCIM provisioning is 预配, following `sys_scim_group_member.display_title` (预配的用户及其所在组). Decommission is 下线. - The notification leaves take the service-messaging bundle's words: 主题, 严重程度 (信息 / 警告 / 严重), 负载, 去重键 and 通道. - `managed_by` options take `sys_metadata`'s words: 管理方, 平台 / 包 / 管理员. - OIDC claims stay `claim`, and help text keeps `scopes`, both as the bundle already writes them (映射:邮箱 claim; 客户端可申请的 scopes …). - "My Memberships" is 我的组织, the account nav's label for the same screen. `Web` is Web 应用, like the sibling `sys_oauth_application.fields.type.options.web`. - `sys_migration` keeps its section's ASCII punctuation. ## Collateral: nine existing ledgers encoded the old copy debt Nine panel ledgers in the same directory asserted floors on the zh-CN provenance table: more than 100 rows, more than 300, and more than 50 echoing objects leaves "to sample". Those floors held only while about 360 zh-CN objects copies existed, so any card that translated them would go red. Each floor's own message states its intent, "provenance table is empty" or "has no echoing objects leaf to sample". Each now asks for greater than 0, with a one-line comment saying why. One more test pinned `sys_oauth_resource.fields.access_token_ttl.label` as an unauthored fill in all three locales. Its own message says "if a translator authored it, this ledger note is stale". It now records that zh-CN is authored (访问令牌 TTL keeps the `TTL` token, the treatment the lifecycle rows give it), while ja-JP and es-ES stay fills. Files: `action-body-panel-`, `bare-type-display-`, `dataset-panel-`, `field-panel-`, `hook-execution-panel-`, `object-collapsed-sections-`, `object-lifecycle-panel-`, `page-interface-panel-` and `report-form-echo-decisions.test.ts`. PR #20485 edits three of these files (`field-panel-`, `object-collapsed-sections-`, `object-lifecycle-panel-`) in different hunks. A local merge-tree run was not taken, so whether the two merge cleanly is not measured. ## Verification Measured at head `08ebcd01d`. That is a true merge of `origin/main` 851af0c, and the CLI closure was rebuilt after the merge. - **`pnpm check:i18n` (H3):** - exit 0 at base; - exit 1 after the leaf edits alone, reported as `platform-objects DRIFTED (1)`. The drifted file was the provenance companion; - exit 0 after `pnpm i18n:extract`. The extract changed only `zh-CN.source-hashes.generated.ts`: −320 rows, 0 added, and the bundle file stayed byte-identical. - **`pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2`:** 56 files and 921 tests passed. Before the collateral edit, the same run had 15 failures across 9 files, all of them the floors above. - **`pnpm --filter @objectstack/platform-objects typecheck`:** exit 0. `tsconfig.test.json` includes the new ledger (`--listFilesOnly`: 1 hit). - **Reverse verification.** Three mutation legs, each through `scripts/ablation-replace.mjs` on `zh-CN.objects.generated.ts`, each red as predicted, and each restored to blob `24cfb9355b89` with `git diff HEAD` empty: 1. `label: "成员数"` changed back to `"Member Count"`: 1 failed / 9 passed, "sys_team.fields.member_count.label reads its en source again". 2. Both `delegated_admin: "受托管理员"` changed back to `"Delegated Admin"`: 2 failed / 8 passed. The verdict row failed, and so did the role-word test ("expected 'Delegated Admin' to match /\p{Script=Han}/u"). 3. The declared echo `google: "Google"` changed to `"谷歌"`: 1 failed / 9 passed, "is a declared echo and must stay the en source". - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `08ebcd01d` printed 61 commands. All 61 were run, and each exit code was written to a file before any pipe. - 60 exited 0 on their first run. - `pnpm check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET (exit 3: workspace `dist/` missing for packages outside the CLI closure). That is not a measurement. Rerun once the `dist/` existed: exit 0. - `--ran` reconciliation: 61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). - `pnpm check:i18n-coverage`: exit 0, "621 baselined untranslated string(s), none new". The ratchet is unchanged, as expected. - NOT MEASURED locally, declared to CI: the 6 workflow-valued families, the 5 path-scheduled CI jobs and the 4 type-check lanes the derivation names. ## Patch round 1 The contract review 5876239898 (PASS at `08ebcd01d`) named one false word and two comment slips. All three are corrected here, as text only; no translated value, assertion or floor changed. - **Changeset:** the translated-class list no longer names placeholders. All four placeholders were decided as echoes, and none was translated. - **Ledger header** (`objects-zh-cn-echo-decisions.test.ts`, comment only): it no longer says the provenance table "kept exactly the 42" as an assertion. It now names what is asserted: every declared echo still has its provenance row, and no pinned translation has one. That the table holds nothing beyond the echoes is measured, and deliberately not asserted. - **This body:** the H4 sentence now says placeholders were decided as the same class and are all declared echoes. The ledger-history sentence now credits `undeclaredEchoes` to the later rounds rather than to `report-dataset-panel-`. - **Merge and readings:** `origin/main` fc0db22 was merged with a true merge commit. At head `927957df4`, `pnpm check:i18n` exited 0 and `pnpm --filter @objectstack/platform-objects exec vitest run src/apps/translations/objects-zh-cn-echo-decisions.test.ts` passed 10 of 10 (lock verdict: command-exit 0). The round-0 gate readings above were taken at `08ebcd01d` and were not re-run. ## Out of scope - The coverage ratchet (see above). - `ja-JP` and `es-ES` carry the same copy class, measured at b810ddb and not edited: - `ja-JP`: 383 copies (`label` 210 · `options.*` 20 · `pluralLabel` 12 · `help` 121 · `description` 12 · `placeholder` 4 · `emptyState` 4); `label` + `options.*` = 230. - `es-ES`: 392 copies (`label` 217 · `options.*` 22 · `pluralLabel` 12 · `help` 121 · `description` 12 · `placeholder` 4 · `emptyState` 4); `label` + `options.*` = 239. ## Acceptance notes - The existing zh-CN bundle renders `Locked Until` two ways, 锁定至 on `sys_user` and 锁定到 on `sys_job_queue`. This card uses 锁定至 for `sys_two_factor` and leaves the pre-existing pair alone. --- _Generated by [Claude Code](https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent de8c973 commit 9bf5e67

13 files changed

Lines changed: 728 additions & 659 deletions
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
fix(platform-objects): the zh-CN platform-object bundle no longer ships English labels, options and help as copies of the source (#20462)
6+
7+
Clause-②: no
8+
9+
A zh-CN console showed English on Setup surfaces: the Invite user dialog listed
10+
the membership roles as 所有者 / 管理员 / Delegated Admin / 成员, and a team record
11+
read MEMBER COUNT. The keys were present in `zh-CN.objects.generated.ts`, but
12+
their values were byte copies of the English source that `os i18n extract
13+
--fill=default` seeds.
14+
15+
- 320 of the 362 zh-CN string leaves that equalled their `en` source are now
16+
translated: labels, plural labels, select options, help, descriptions and
17+
empty states. `delegated_admin` reads 受托管理员 on both
18+
`sys_member` and `sys_invitation`, the word the console already uses for that
19+
role.
20+
- The other 42 stay English by design, and each is recorded with its reason in
21+
`objects-zh-cn-echo-decisions.test.ts`: sign-in provider brands (Google, GitHub,
22+
…), the bare `ID` initialism, protocol names (JWKS, IdP SSO URL, Message-ID,
23+
UI / API), the OAuth credential names in the create-application result dialog,
24+
and placeholders that show a value the admin types.
25+
26+
`zh-CN.source-hashes.generated.ts` was regenerated by `pnpm i18n:extract`: it
27+
records a leaf only while the leaf is a copy of its source, so it now lists
28+
exactly those 42. `ja-JP` and `es-ES` are unchanged.

‎packages/platform-objects/src/apps/translations/action-body-panel-echo-decisions.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,9 @@ describe('#19403 round 5 — the provenance table agrees these leaves are now au
528528
it(`${locale}: no decided leaf is still recorded as an extractor fill`, () => {
529529
// Lit — the table really loaded, so "no entry" cannot pass by the import
530530
// having come back empty.
531-
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(100);
531+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
532+
// its declared English-by-design leaves, so the table is small by design.
533+
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(0);
532534
const stillFilled = DECISIONS.filter((d) => table[provenanceKey(d)] !== undefined).map(idOf);
533535
expect(stillFilled, 'these leaves are still byte copies of their source revision').toEqual([]);
534536
});

‎packages/platform-objects/src/apps/translations/bare-type-display-echo-decisions.test.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,9 @@ describe('#19403 round 7 — the provenance table agrees these leaves are now au
703703
it(`${locale}: no decided leaf is still recorded as an extractor fill`, () => {
704704
// Lit — the table really loaded, so "no entry" cannot pass by the import
705705
// having come back empty.
706-
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(100);
706+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
707+
// its declared English-by-design leaves, so the table is small by design.
708+
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(0);
707709
const stillFilled = DECISIONS.filter((d) => table[provenanceKey(d)] !== undefined).map(idOf);
708710
expect(stillFilled, 'these leaves are still byte copies of their source revision').toEqual([]);
709711
});
@@ -754,7 +756,9 @@ describe('#19403 round 7 — the provenance table agrees these leaves are now au
754756
// of my twelve is still a fill", but "no metadata type's own display pair
755757
// is still a fill, anywhere in this table". Lit by the table being large,
756758
// so a zero cannot come from an empty import.
757-
expect(Object.keys(table).length).toBeGreaterThan(100);
759+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
760+
// its declared English-by-design leaves, so the table is small by design.
761+
expect(Object.keys(table).length).toBeGreaterThan(0);
758762
const typeLevel = Object.keys(table).filter((k) => /^metadataForms\.[^.]+\.(label|description)$/.test(k));
759763
expect(typeLevel, 'a metadata type display pair is still an extractor fill').toEqual([]);
760764
});

‎packages/platform-objects/src/apps/translations/dataset-panel-echo-decisions.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,9 @@ describe('#19403 round 3 — the provenance table agrees these leaves are now au
556556
it(`${locale}: no decided leaf is still recorded as an extractor fill`, () => {
557557
// Lit — the table really loaded, so "no entry" cannot pass by the import
558558
// having come back empty.
559-
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(100);
559+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
560+
// its declared English-by-design leaves, so the table is small by design.
561+
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(0);
560562
const stillFilled = DECISIONS.filter((d) => table[provenanceKey(d)] !== undefined).map(idOf);
561563
expect(stillFilled, 'these leaves are still byte copies of their source revision').toEqual([]);
562564
});

‎packages/platform-objects/src/apps/translations/field-panel-echo-decisions.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,9 @@ describe('#19403 round 4 — the provenance table agrees these leaves are now au
557557
it(`${locale}: no decided leaf is still recorded as an extractor fill`, () => {
558558
// Lit — the table really loaded, so "no entry" cannot pass by the import
559559
// having come back empty.
560-
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(100);
560+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
561+
// its declared English-by-design leaves, so the table is small by design.
562+
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(0);
561563
const stillFilled = DECISIONS.filter((d) => table[provenanceKey(d)] !== undefined).map(idOf);
562564
expect(stillFilled, 'these leaves are still byte copies of their source revision').toEqual([]);
563565
});

‎packages/platform-objects/src/apps/translations/hook-execution-panel-echo-decisions.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,9 @@ describe('#19403 round 6 — the provenance table agrees these leaves are now au
669669
it(`${locale}: no decided leaf is still recorded as an extractor fill`, () => {
670670
// Lit — the table really loaded, so "no entry" cannot pass by the import
671671
// having come back empty.
672-
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(100);
672+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
673+
// its declared English-by-design leaves, so the table is small by design.
674+
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(0);
673675
const stillFilled = DECISIONS.filter((d) => table[provenanceKey(d)] !== undefined).map(idOf);
674676
expect(stillFilled, 'these leaves are still byte copies of their source revision').toEqual([]);
675677
});

‎packages/platform-objects/src/apps/translations/object-collapsed-sections-echo-decisions.test.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,9 @@ describe('#19403 round 8 — the provenance table agrees these leaves are now au
770770
it(`${locale}: no decided leaf is still recorded as an extractor fill`, () => {
771771
// Lit — the table really loaded, so "no entry" cannot pass by the import
772772
// having come back empty.
773-
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(100);
773+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
774+
// its declared English-by-design leaves, so the table is small by design.
775+
expect(Object.keys(table).length, `${locale} provenance table is empty`).toBeGreaterThan(0);
774776
const stillFilled = DECISIONS.filter((d) => table[provenanceKey(d)] !== undefined).map(idOf);
775777
expect(stillFilled, 'these leaves are still byte copies of their source revision').toEqual([]);
776778
});
@@ -807,7 +809,9 @@ describe('#19403 round 8 — the provenance table agrees these leaves are now au
807809
const authored = [...enObjectPaths]
808810
.filter(([path, en]) => localeObjects.has(path) && localeObjects.get(path) !== en)
809811
.map(([path]) => path);
810-
expect(echoing.length, `${locale} has no echoing objects leaf to sample`).toBeGreaterThan(50);
812+
// At least one of each is what makes a positive AND a negative; since
813+
// #20462 zh-CN's echoing objects leaves are its 42 declared English ones.
814+
expect(echoing.length, `${locale} has no echoing objects leaf to sample`).toBeGreaterThan(0);
811815
expect(authored.length, `${locale} has no authored objects leaf to sample`).toBeGreaterThan(50);
812816
expect(
813817
echoing.filter((path) => table[`objects.${path}`] === undefined),
@@ -848,7 +852,9 @@ describe('#19403 round 8 — the provenance table agrees these leaves are now au
848852
// The strongest form the verdict above can take for this class: not "none
849853
// of my eleven", but "nothing this population reaches, outside the
850854
// declared deferral". Lit by the table being large.
851-
expect(Object.keys(table).length).toBeGreaterThan(100);
855+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
856+
// its declared English-by-design leaves, so the table is small by design.
857+
expect(Object.keys(table).length).toBeGreaterThan(0);
852858
const stillFilled = PANEL_LEAVES.filter(
853859
(l) => !inDeferral(l) && table[`metadataForms.object.fields.${l.path}.${l.prop}`] !== undefined,
854860
).map((l) => `${l.path}.${l.prop}`);

‎packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts‎

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,15 +1292,24 @@ describe('#19403 round 10 — ⚠️⚠️ the phantom-translation trap, asserte
12921292
});
12931293

12941294
it('⭐ the near-twin that was LOOKED UP AND REFUSED is asserted to be a fill, not a decision', () => {
1295-
// `Access Token TTL` is byte-identical in all three locales and reads like a
1296-
// standing decision to keep TTL verbatim. It is an unauthored fill in every
1297-
// locale — including zh-CN — and the provenance tables say so. Asserted so
1298-
// that no later round leans on it.
1295+
// `Access Token TTL` was byte-identical in all three locales and read like a
1296+
// standing decision to keep TTL verbatim. It was an unauthored fill in every
1297+
// locale, and the provenance tables said so. Asserted so that no later round
1298+
// leans on it. #20462 then AUTHORED the zh-CN leaf (访问令牌 TTL): the
1299+
// concept is rendered and the initialism survives, the same treatment the
1300+
// lifecycle rows give TTL. ja-JP and es-ES are still fills.
12991301
const key = 'sys_oauth_resource.fields.access_token_ttl.label';
13001302
const en = FLAT_OBJECTS.get('en')!.get(key);
13011303
expect(en).toBe('Access Token TTL');
13021304
for (const [locale, table] of PROVENANCE) {
1303-
expect(FLAT_OBJECTS.get(locale)!.get(key), `${locale} ${key}`).toBe(en);
1305+
const value = FLAT_OBJECTS.get(locale)!.get(key);
1306+
if (locale === 'zh-CN') {
1307+
expect(value, `${locale} ${key} reads its en source again`).not.toBe(en);
1308+
expect(carriesToken(value!, 'TTL'), `${locale} dropped the machine token`).toBe(true);
1309+
expect(table[`objects.${key}`], `${locale} still records the authored ${key} as a fill`).toBeUndefined();
1310+
continue;
1311+
}
1312+
expect(value, `${locale} ${key}`).toBe(en);
13041313
expect(
13051314
table[`objects.${key}`],
13061315
`${locale} no longer records ${key} as a fill — if a translator authored it, this ledger note is stale`,
@@ -1317,7 +1326,9 @@ describe('#19403 round 10 — the SECOND WITNESS, over the `metadataForms.`-pref
13171326
// exists exactly while the leaf IS a byte copy of the source revision, so
13181327
// a leaf this round authored must have lost its row.
13191328
const keys = Object.keys(table);
1320-
expect(keys.length, 'the provenance table is empty — the witness cannot testify').toBeGreaterThan(300);
1329+
// Non-empty, not a debt size: since #20462 zh-CN's objects slice holds only
1330+
// its declared English-by-design leaves, so the table is small by design.
1331+
expect(keys.length, 'the provenance table is empty — the witness cannot testify').toBeGreaterThan(0);
13211332
const metadataFormRows = keys.filter((k) => k.startsWith('metadataForms.'));
13221333
// The reading: NOTHING under the metadataForms. prefix is a fill any more.
13231334
expect(metadataFormRows, `${locale} still records a metadata-form leaf as an unauthored fill`).toEqual([]);
@@ -1342,7 +1353,9 @@ describe('#19403 round 10 — the SECOND WITNESS, over the `metadataForms.`-pref
13421353
const loc = FLAT_OBJECTS.get(locale)!;
13431354
const echoing = [...en].filter(([k, v]) => loc.get(k) === v).map(([k]) => k);
13441355
const authored = [...en].filter(([k, v]) => loc.get(k) !== undefined && loc.get(k) !== v).map(([k]) => k);
1345-
expect(echoing.length, `${locale} has no echoing objects leaf to sample`).toBeGreaterThan(50);
1356+
// At least one of each is what makes a positive AND a negative; since
1357+
// #20462 zh-CN's echoing objects leaves are its 42 declared English ones.
1358+
expect(echoing.length, `${locale} has no echoing objects leaf to sample`).toBeGreaterThan(0);
13461359
expect(authored.length, `${locale} has no authored objects leaf to sample`).toBeGreaterThan(50);
13471360
expect(
13481361
echoing.filter((k) => table[`objects.${k}`] === undefined),

0 commit comments

Comments
 (0)