Skip to content

Commit 6af2901

Browse files
huangyiireneclaude
andauthored
fix(platform-objects): decide the report/dataset panel en-echoes per leaf, and pin the decisions as a ledger (#19401)
Fixes #19355 Clause-②: no ## What this is The card's deliverable is a **per-leaf judgement with its reason recorded**, not twelve translations. Every leaf below was decided on its own evidence; the verdicts and their reasons live in a ledger the suite reads (`report-dataset-panel-echo-decisions.test.ts`), not only in this body. ## The reading, re-taken on this base Re-derived mechanically on the worktree base `3ff6ddf1e`, over the four keys the card names, in `zh-CN` / `ja-JP` / `es-ES`: - **12 label leaves byte-identical to their `en` source — the card's count is CONFIRMED**, key for key and locale for locale. - **9 further `helpText` leaves at the same four keys** were also byte copies (`report.fields.dataset` / `values` / `rows`, three locales each). They are the same authored unit as the label they sit on, so a fix that left them English would leave a panel whose field name is Chinese and whose tooltip is not. - `dataset.fields.measures.helpText` was **already translated** in all three locales. That leaf is this change's negative control and is recorded as such. ⇒ decided population: 4 keys × 8 string leaves × 3 locales = **24 decisions**, of which 21 were echoes and 3 were already authored. ## The instrument, with both controls This package ships its own provenance table, `PACKAGE/src/apps/translations/LOCALE.source-hashes.generated.ts`: an entry exists exactly while that leaf is still a byte copy of the source revision. - positive control — every one of the 21 echoing leaves carried an entry, in all three locales; - negative control — `dataset.fields.measures.helpText`, the one leaf here a translator had already written, carried none in any of them. After the translations, `pnpm i18n:extract` dropped exactly 21 rows (7 keys × 3 locales), 0 added. The table now records these leaves as authored rather than copied. ## Per-leaf decisions Every verdict below is `translate` — the echo was an unauthored extractor fill. That is a conclusion per leaf, not a policy: the ledger's `echo` verdict exists and costs one recorded sentence per locale, and no leaf here earned it. | leaf | why it is a fill, not a rendering | |:---|:---| | `report.fields.dataset` (label) | The same panel's `blocks.dataset` carries authored text in all three locales — 数据集 / データセット / Conjunto de datos — landed deliberately by #19345. Note this settles the one case triage flagged as possibly legitimate (`Dataset` in `es-ES`): this catalog's own authored answer for that word, in that locale, is "Conjunto de datos". | | `report.fields.dataset` (helpText) | Prose, not a term of art, and a byte copy in all three locales while the sibling helpTexts on the same panel (`type`, `columns`, `order`, `chart`) were authored. | | `report.fields.values` (label) | `blocks.values` on the same panel is authored 度量 / メジャー / Medidas. | | `report.fields.values` (helpText) | Prose. Its own sibling `order.helpText` names the same two vocabularies (rows/columns dimensions, values measures) in authored text in every locale — the vocabulary existed; this leaf never got it. | | `report.fields.rows` (label) | `blocks.rows` is authored 行 / 行 / Filas, and the adjacent top-level `columns` is authored 列 / 列 / Columnas on the same panel. | | `report.fields.rows` (helpText) | The neighbouring `columns.helpText` — the same sentence for the other axis — is authored in all three locales. | | `dataset.fields.measures` (label) | The sharpest case: the **same leaf** carried a hand-authored `helpText` in all three locales while its `label` was a byte copy. One leaf cannot be a decision and a fill at once. | | `dataset.fields.measures` (helpText) | Already authored before this card. No change; recorded because it is the negative control above. | The shared shape of the first seven: a translator who judged the English word right at the top level would not have written the translated one a few entries below it, in the same catalog, on the same panel, for the same concept. ## The pin question, answered **The derived pin is not widened. A listed ledger is added instead.** `repeater-row-properties.test.ts` derives its population from the forms, and that derivation is the reason it is good: a repeater enumerated tomorrow is red the day it lands. Measured on this base: - its population is **124** enumerated repeater row properties, and **0** of them is an en-echo — these four keys are top-level form fields, outside it; - the only widening that would reach them is "all declared form-field paths", which is **417** paths — and that population is red on **230 further echo leaves across 82 keys** today, none of them decided by this card. A pin that lands red is not a pin, and widening it would also retro-scope #17508's carrier ruling. So the derived population stays as #17508 drew it, and the DECIDED leaves are enumerated in a new file whose header states the split. This is the ledger row the card asks for rather than prose: a future deliberate echo is a `verdict: 'echo'` row that must carry its own per-locale reason, and the suite refuses one without it. Each row is also pinned to the `en` source it was decided against, so a reworded source reds the file instead of leaving a decision standing over text nobody judged. ## Reverse verification Both mutations ran through `scripts/ablation-replace.mjs` (anchor must hit; on-disk blob before/after; restore proven equal to the `HEAD` blob with `git diff HEAD` empty). Baseline first: 7 passed. - **A — put one decided leaf back to its `en` source** (`zh-CN` `report.fields.values.label`): blob `34c532953acf` to `3f7269b0789b`, run **1 failed / 6 passed**, `AssertionError: zh-CN report.fields.values.label reads its en source again`. Restored, blob back to `34c532953acf`. - **B — reword the `en` source a ledger row is pinned to** (`Values` to `Value`): blob `b8a246889186` to `689bf01a679d`, run **2 failed / 5 passed** — the source pin AND the dark control (`expected 7 to be 8`), so the control is live too. Restored, blob back to `b8a246889186`. An earlier attempt at A is reported as a no-op rather than quietly re-run: the mutation and its restore trap sat in one shell that exited between them, so the file was already restored when the suite ran and the green it printed measured nothing. ## The generated-bundle hazard the claim flagged These bundles are generated and `git check-attr merge` reads `unspecified` for all of them (control: `packages/spec/spec-changes.json` reads `os-regen`), so two branches adding i18n keys would merge clean and drop one side. Nothing collides here — this change adds and removes **no keys at all**, only leaf values — and the bundles were regenerated with the repo's own tooling, never by hand: pnpm i18n:extract It rewrote all eleven bundles; the three translated `metadata-forms` bundles round-tripped byte-identical to the hand-edited values, which is itself the evidence that the edits are exactly what the extractor's merge baseline produces. ## Changeset, measured `@objectstack/platform-objects` is not `private`, `publishConfig.access` is `public`, and `files[]` ships `dist`. Built, then grepped `dist` for a new string with a pre-existing translated leaf on the same panel as the positive control: both reach the same five published entry points (`index.js`, `index.mjs`, `plugin.js`, `plugin.mjs`, `metadata-translations/index.*`). A test-only string reaches none of them. ⇒ the diff publishes, `skip-changeset` would be wrong, and a `patch` changeset is included. ## Verification Head `68049082b`. - `pnpm --filter @objectstack/platform-objects test` — 43 files, 599 tests passed (was 43/592 with the new file's 7 tests added; the translations directory alone goes 9 files/108 tests to 10/115). - `pnpm --filter @objectstack/platform-objects typecheck` — green, including `check:test-typecheck`. The new test file is in `tsconfig.test.json`'s program (counted with `--listFiles`) and, by design, not in the main one. - Derived gate families: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` — **59 derived, 59 run, 0 NOT-MEASURED**, every one recorded with its exit code. Includes `check:i18n`, `check:i18n-stale-fill`, `check:nul-bytes`, `check:published-files`, `check:type-check-coverage`, `check:type-check-debt`, `check:cross-package-test-inputs`, `check:test-source-alias`. - Two adjacent i18n gates not in the derivation, run anyway: `check:i18n-coverage` (13 configs, 621 baselined, none new) and `check:i18n-walk-parity` — both green. - `pnpm lint` — the whole repo, exit 0, no output. - One honest note on the sweep: `check:dual-build-cjs-loads` answered `PREREQUISITE NOT MET` (exit 3) on its first run because twelve packages had no `dist` yet. That is not a red and was not recorded as one; once the workspace was fully built it ran for real and passed (104 entry points across 67 packages). ## Acceptance notes Noted, not filed — observations, no defect named: - The `dataset` panel's own `label`, `description` and all four `sections` are still `en` echoes in all three locales, as are seven further top-level `dataset.fields.*` keys. Same class as this card, outside its four keys. - `report.sections.dataset_binding` (label and description) and `report.fields.drilldown` / `runtimeFilter` are echoes on the panel this change touches. To file (reproducible defect, class (a)) — handed to the seat, not filed by this PR: - **The metadata-form catalogs carry 82 further en-echo label keys (230 leaves) beyond the four #19355 decides.** Measured on `3ff6ddf1e` over all 417 declared form-field paths; 0 of them are inside `repeater-row-properties.test.ts`'s 124-property population, so no pin sees them. A user in a translated locale reads English on those keys. It is the same class as this card and explicitly not mechanical — each key needs the same per-leaf judgement — so it is not taken in-place here. Dedupe words: `en echo translated locale` · `metadata form field label untranslated` · `dataset.fields.dimensions object.fields.enable page.fields.interfaceConfig` · `declared form field echo census` · `translated leaf copy of source`. --- _Generated by [Claude Code](https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0870fb5 commit 6af2901

8 files changed

Lines changed: 252 additions & 42 deletions

‎.changeset/lucky-pugs-repeat.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
Translate the report and dataset form panel leaves that shipped their English source in every locale
6+
7+
Four metadata-form keys — `report.fields.dataset`, `report.fields.values`, `report.fields.rows` and `dataset.fields.measures` — carried labels byte-identical to their `en` source in `zh-CN`, `ja-JP` and `es-ES`, so an author working in a translated locale read English on those two panels while everything around them was translated. Twelve label leaves and nine `helpText` leaves at the same four keys are now translated; each was judged individually, and the verdicts with their reasons are pinned in `report-dataset-panel-echo-decisions.test.ts`. No key was added, removed or renamed — the bundles' shape is unchanged.

‎packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,16 +1398,16 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
13981398
helpText: "Tipo de informe: tabular/summary/matrix/joined"
13991399
},
14001400
dataset: {
1401-
label: "Dataset",
1402-
helpText: "Dataset to bind (measures/dimensions come from its semantic layer)"
1401+
label: "Conjunto de datos",
1402+
helpText: "Conjunto de datos a vincular (las medidas/dimensiones provienen de su capa semántica)"
14031403
},
14041404
values: {
1405-
label: "Values",
1406-
helpText: "Measure names (from the dataset) to display"
1405+
label: "Medidas",
1406+
helpText: "Nombres de medida (del conjunto de datos) que se mostrarán"
14071407
},
14081408
rows: {
1409-
label: "Rows",
1410-
helpText: "Dimension names (from the dataset) to group rows by"
1409+
label: "Filas",
1410+
helpText: "Nombres de dimensión (del conjunto de datos) por los que agrupar las filas"
14111411
},
14121412
columns: {
14131413
label: "Columnas",
@@ -1540,7 +1540,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
15401540
label: "Granularidad de fecha"
15411541
},
15421542
measures: {
1543-
label: "Measures",
1543+
label: "Medidas",
15441544
helpText: "Cada medida: nombre, agregación, campo (opcional para count) y formato de visualización/moneda"
15451545
},
15461546
"measures.name": {

‎packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export const esESGeneratedSourceHashes: Readonly<Record<string, string>> = {
4747
"metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a",
4848
"metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3",
4949
"metadataForms.dataset.fields.label.label": "c680364257b11569",
50-
"metadataForms.dataset.fields.measures.label": "395dac46abcf1943",
5150
"metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a",
5251
"metadataForms.dataset.fields.name.label": "5274417ff1ccaf01",
5352
"metadataForms.dataset.fields.object.helpText": "09a85579054a7197",
@@ -195,16 +194,10 @@ export const esESGeneratedSourceHashes: Readonly<Record<string, string>> = {
195194
"metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1",
196195
"metadataForms.page.sections.interface.description": "7e60b977279a05dc",
197196
"metadataForms.page.sections.interface.label": "e2280abc5267ddec",
198-
"metadataForms.report.fields.dataset.helpText": "3cd157f274c985df",
199-
"metadataForms.report.fields.dataset.label": "19647dcb84ce82d3",
200197
"metadataForms.report.fields.drilldown.helpText": "778174eac8057764",
201198
"metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5",
202-
"metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5",
203-
"metadataForms.report.fields.rows.label": "319af74cc41ea823",
204199
"metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40",
205200
"metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa",
206-
"metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1",
207-
"metadataForms.report.fields.values.label": "3edea9880d8fee80",
208201
"metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd",
209202
"metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56",
210203
"metadataForms.seed.description": "c22921feb0f06273",

‎packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,16 +1398,16 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
13981398
helpText: "レポート種別: tabular/summary/matrix/joined"
13991399
},
14001400
dataset: {
1401-
label: "Dataset",
1402-
helpText: "Dataset to bind (measures/dimensions come from its semantic layer)"
1401+
label: "データセット",
1402+
helpText: "バインドするデータセット(メジャー/ディメンションはセマンティックレイヤーから取得)"
14031403
},
14041404
values: {
1405-
label: "Values",
1406-
helpText: "Measure names (from the dataset) to display"
1405+
label: "メジャー",
1406+
helpText: "表示するメジャー名(データセットから取得)"
14071407
},
14081408
rows: {
1409-
label: "Rows",
1410-
helpText: "Dimension names (from the dataset) to group rows by"
1409+
label: "行",
1410+
helpText: "行をグループ化するディメンション名(データセットから取得)"
14111411
},
14121412
columns: {
14131413
label: "列",
@@ -1540,7 +1540,7 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
15401540
label: "日付の粒度"
15411541
},
15421542
measures: {
1543-
label: "Measures",
1543+
label: "メジャー",
15441544
helpText: "各メジャー:名前、集計関数、フィールド(count の場合は省略可)、表示形式/通貨"
15451545
},
15461546
"measures.name": {

‎packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export const jaJPGeneratedSourceHashes: Readonly<Record<string, string>> = {
4747
"metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a",
4848
"metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3",
4949
"metadataForms.dataset.fields.label.label": "c680364257b11569",
50-
"metadataForms.dataset.fields.measures.label": "395dac46abcf1943",
5150
"metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a",
5251
"metadataForms.dataset.fields.name.label": "5274417ff1ccaf01",
5352
"metadataForms.dataset.fields.object.helpText": "09a85579054a7197",
@@ -195,16 +194,10 @@ export const jaJPGeneratedSourceHashes: Readonly<Record<string, string>> = {
195194
"metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1",
196195
"metadataForms.page.sections.interface.description": "7e60b977279a05dc",
197196
"metadataForms.page.sections.interface.label": "e2280abc5267ddec",
198-
"metadataForms.report.fields.dataset.helpText": "3cd157f274c985df",
199-
"metadataForms.report.fields.dataset.label": "19647dcb84ce82d3",
200197
"metadataForms.report.fields.drilldown.helpText": "778174eac8057764",
201198
"metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5",
202-
"metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5",
203-
"metadataForms.report.fields.rows.label": "319af74cc41ea823",
204199
"metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40",
205200
"metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa",
206-
"metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1",
207-
"metadataForms.report.fields.values.label": "3edea9880d8fee80",
208201
"metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd",
209202
"metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56",
210203
"metadataForms.seed.description": "c22921feb0f06273",
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// #19355 — the DECISION LEDGER for the report / dataset panel en-echoes.
4+
//
5+
// An en-echo is not automatically a defect: a leaf that reads its English
6+
// source may be an unauthored extractor fill, or it may be the right rendering
7+
// for that locale. The two are byte-identical, so the distinction cannot be
8+
// recovered from the catalog later — it has to be RECORDED when someone looks.
9+
// This file is that record for the four keys #19355 names, one row per string
10+
// leaf, each with the verdict and the reason it was reached.
11+
//
12+
// Why the population here is a LIST while `repeater-row-properties.test.ts`
13+
// DERIVES its own. That file's population is the enumerated repeater row
14+
// properties — 124 of them, measured on this base — and these four keys are
15+
// top-level form fields, outside it. Widening that derivation to all declared
16+
// form-field paths (417, same base) would be red on 230 further echo leaves
17+
// across 82 keys the moment it landed: neighbouring debt nobody has decided
18+
// yet. A pin that lands red is not a pin, so the derived population stays as
19+
// #17508 drew it, and the DECIDED leaves are enumerated here instead. A key
20+
// enters this list when someone judges it, never before.
21+
//
22+
// The instrument that measured the verdicts, with both of its controls, is the
23+
// package's own provenance table `<locale>.source-hashes.generated.ts`: an
24+
// entry exists exactly while the leaf is still a byte copy of the source
25+
// revision. Every leaf this card found echoing carried one in all three
26+
// locales (positive control); `dataset.fields.measures.helpText`, the one leaf
27+
// at these keys that a translator had already written, carried none in any of
28+
// them (negative control). `pnpm i18n:extract` dropped the 21 rows for the
29+
// leaves this card translated, so the table now records them as authored.
30+
//
31+
// ⛔ Do not add a row here to make a red go away. A row is a decision someone
32+
// took about one leaf; the `echo` verdict needs its own per-locale reason
33+
// precisely so that recording "the English is right here" costs a sentence.
34+
35+
import { describe, it, expect } from 'vitest';
36+
37+
import { enMetadataForms } from './en.metadata-forms.generated.js';
38+
import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js';
39+
import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js';
40+
import { esESMetadataForms } from './es-ES.metadata-forms.generated.js';
41+
42+
const TRANSLATED_LOCALES: ReadonlyArray<readonly [string, Record<string, any>]> = [
43+
['zh-CN', zhCNMetadataForms as Record<string, any>],
44+
['ja-JP', jaJPMetadataForms as Record<string, any>],
45+
['es-ES', esESMetadataForms as Record<string, any>],
46+
];
47+
48+
/** `translate` — the echo was an unauthored fill. `echo` — the English IS the rendering. */
49+
type Verdict = 'translate' | 'echo';
50+
51+
interface Decision {
52+
/** Metadata type owning the form panel. */
53+
type: string;
54+
/** The bundle key under `<type>.fields`. */
55+
key: string;
56+
/** Which string leaf of that entry this row decides. */
57+
prop: 'label' | 'helpText';
58+
/**
59+
* The `en` source the verdict was taken against. Held equal to the live
60+
* bundle, so a reworded source reds this file instead of leaving a decision
61+
* standing over text nobody judged.
62+
*/
63+
en: string;
64+
/** The verdict per translated locale. */
65+
verdict: Readonly<Record<string, Verdict>>;
66+
/** Why — recorded for `translate` as much as for `echo`. */
67+
reason: string;
68+
/** Required for every `echo` verdict: why the English is right in THAT locale. */
69+
departures?: Readonly<Record<string, string>>;
70+
}
71+
72+
const ALL_TRANSLATE: Readonly<Record<string, Verdict>> = {
73+
'zh-CN': 'translate',
74+
'ja-JP': 'translate',
75+
'es-ES': 'translate',
76+
};
77+
78+
const SIBLING_PRECEDENT =
79+
'The same panel carries the identical concept one repeater level down, with text #19345 authored deliberately in all three locales. A translator who judged the English word right at the top level would not have written the translated one a few entries below it, so the echo was a fill, not a rendering.';
80+
81+
const DECISIONS: readonly Decision[] = [
82+
{
83+
type: 'report',
84+
key: 'dataset',
85+
prop: 'label',
86+
en: 'Dataset',
87+
verdict: ALL_TRANSLATE,
88+
reason: `${SIBLING_PRECEDENT} Precedent: report.fields['blocks.dataset'].`,
89+
},
90+
{
91+
type: 'report',
92+
key: 'dataset',
93+
prop: 'helpText',
94+
en: 'Dataset to bind (measures/dimensions come from its semantic layer)',
95+
verdict: ALL_TRANSLATE,
96+
reason:
97+
'Prose, not a term of art, and it was a byte copy in all three locales while the sibling helpTexts on the same panel (type, columns, order, chart) were authored. A fill.',
98+
},
99+
{
100+
type: 'report',
101+
key: 'values',
102+
prop: 'label',
103+
en: 'Values',
104+
verdict: ALL_TRANSLATE,
105+
reason: `${SIBLING_PRECEDENT} Precedent: report.fields['blocks.values'].`,
106+
},
107+
{
108+
type: 'report',
109+
key: 'values',
110+
prop: 'helpText',
111+
en: 'Measure names (from the dataset) to display',
112+
verdict: ALL_TRANSLATE,
113+
reason:
114+
'Prose. Its own sibling order.helpText names the same two vocabularies (rows/columns dimensions, values measures) in authored text in every locale, so the vocabulary existed and this leaf simply never got it.',
115+
},
116+
{
117+
type: 'report',
118+
key: 'rows',
119+
prop: 'label',
120+
en: 'Rows',
121+
verdict: ALL_TRANSLATE,
122+
reason: `${SIBLING_PRECEDENT} Precedent: report.fields['blocks.rows'], and the adjacent top-level columns is authored on the same panel.`,
123+
},
124+
{
125+
type: 'report',
126+
key: 'rows',
127+
prop: 'helpText',
128+
en: 'Dimension names (from the dataset) to group rows by',
129+
verdict: ALL_TRANSLATE,
130+
reason:
131+
'Prose, and the neighbouring columns.helpText — the same sentence for the other axis — is authored in all three locales. Nothing distinguishes the two but one having been reached.',
132+
},
133+
{
134+
type: 'dataset',
135+
key: 'measures',
136+
prop: 'label',
137+
en: 'Measures',
138+
verdict: ALL_TRANSLATE,
139+
reason:
140+
'The sharpest case: the SAME leaf carried a hand-authored helpText in all three locales while its label was a byte copy of the source. One leaf cannot be a decision and a fill at once, and the helpText settles which one it was.',
141+
},
142+
{
143+
type: 'dataset',
144+
key: 'measures',
145+
prop: 'helpText',
146+
en: 'Each: name, aggregate, field (optional for count), and display format/currency',
147+
verdict: ALL_TRANSLATE,
148+
reason:
149+
'Already authored before this card in all three locales — recorded because it is this ledger\'s negative control: the one leaf at these four keys that held no provenance-table entry, which is what an authored leaf looks like.',
150+
},
151+
];
152+
153+
/** The leaf a row points at, in a given catalog. */
154+
function leafOf(forms: Record<string, any>, d: Decision): unknown {
155+
return forms[d.type]?.fields?.[d.key]?.[d.prop];
156+
}
157+
158+
describe('#19355 — the ledger itself (controls before verdicts)', () => {
159+
it('decides every string leaf of the four keys the card names, and nothing else', () => {
160+
// Lit — the ledger is the size it claims: four keys, eight leaves, three
161+
// locales, 24 decisions.
162+
expect(DECISIONS.length).toBe(8);
163+
expect(new Set(DECISIONS.map((d) => `${d.type}.fields.${d.key}`))).toEqual(
164+
new Set(['report.fields.values', 'report.fields.rows', 'report.fields.dataset', 'dataset.fields.measures']),
165+
);
166+
expect(DECISIONS.flatMap((d) => Object.keys(d.verdict)).length).toBe(24);
167+
for (const d of DECISIONS) {
168+
expect(Object.keys(d.verdict).sort(), `${d.type}.${d.key}.${d.prop} names every translated locale`).toEqual([
169+
'es-ES',
170+
'ja-JP',
171+
'zh-CN',
172+
]);
173+
}
174+
});
175+
176+
it('every row is pinned to the live `en` source it was decided against', () => {
177+
for (const d of DECISIONS) {
178+
expect(
179+
(enMetadataForms as Record<string, any>)[d.type]?.fields?.[d.key]?.[d.prop],
180+
`en ${d.type}.fields.${d.key}.${d.prop} moved — re-judge the decision, do not refresh this row`,
181+
).toBe(d.en);
182+
}
183+
});
184+
185+
it('the echo predicate can say "echo" — fed the `en` catalog, it flags every row', () => {
186+
// Dark. `translated !== en` is the whole verdict test below; run it against
187+
// the source catalog itself and it must fail for all 8 rows, or a green
188+
// verdict run means nothing.
189+
const flagged = DECISIONS.filter((d) => leafOf(enMetadataForms as Record<string, any>, d) === d.en);
190+
expect(flagged.length).toBe(DECISIONS.length);
191+
});
192+
193+
it('every decision records a reason, and every `echo` records its per-locale departure', () => {
194+
for (const d of DECISIONS) {
195+
expect(d.reason.length, `${d.type}.fields.${d.key}.${d.prop} records no reason`).toBeGreaterThan(40);
196+
for (const [locale, verdict] of Object.entries(d.verdict)) {
197+
if (verdict !== 'echo') continue;
198+
const departure = d.departures?.[locale] ?? '';
199+
expect(
200+
departure.length,
201+
`${locale} ${d.type}.fields.${d.key}.${d.prop} is a declared echo with no reason — an undeclared echo is the debt this ledger exists to tell apart`,
202+
).toBeGreaterThan(40);
203+
}
204+
}
205+
});
206+
});
207+
208+
describe('#19355 — the catalogs hold what the ledger decided', () => {
209+
for (const [locale, forms] of TRANSLATED_LOCALES) {
210+
it(`${locale}: every decided leaf matches its verdict`, () => {
211+
for (const d of DECISIONS) {
212+
const id = `${locale} ${d.type}.fields.${d.key}.${d.prop}`;
213+
const value = leafOf(forms, d);
214+
expect(typeof value, `${id} is missing from the catalog`).toBe('string');
215+
expect((value as string).length, `${id} is empty`).toBeGreaterThan(0);
216+
if (d.verdict[locale] === 'translate') {
217+
expect(value, `${id} reads its en source again — the decision was that this echo is a fill`).not.toBe(d.en);
218+
} else {
219+
expect(value, `${id} is a declared echo and must stay the en source`).toBe(d.en);
220+
}
221+
}
222+
});
223+
}
224+
});

‎packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,16 +1398,16 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
13981398
helpText: "报表类型:tabular / summary / matrix / joined"
13991399
},
14001400
dataset: {
1401-
label: "Dataset",
1402-
helpText: "Dataset to bind (measures/dimensions come from its semantic layer)"
1401+
label: "数据集",
1402+
helpText: "要绑定的数据集(度量/维度来自其语义层)"
14031403
},
14041404
values: {
1405-
label: "Values",
1406-
helpText: "Measure names (from the dataset) to display"
1405+
label: "度量",
1406+
helpText: "要展示的度量名(来自该数据集)"
14071407
},
14081408
rows: {
1409-
label: "Rows",
1410-
helpText: "Dimension names (from the dataset) to group rows by"
1409+
label: "行",
1410+
helpText: "用于分组行的维度名(来自该数据集)"
14111411
},
14121412
columns: {
14131413
label: "列",
@@ -1540,7 +1540,7 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
15401540
label: "日期粒度"
15411541
},
15421542
measures: {
1543-
label: "Measures",
1543+
label: "度量",
15441544
helpText: "每个度量:名称、聚合函数、字段(count 时可省略)、显示格式/货币"
15451545
},
15461546
"measures.name": {

0 commit comments

Comments
 (0)