Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/lucky-pugs-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@objectstack/platform-objects': patch
---

Translate the report and dataset form panel leaves that shipped their English source in every locale

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.
Original file line number Diff line number Diff line change
Expand Up @@ -1398,16 +1398,16 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
helpText: "Tipo de informe: tabular/summary/matrix/joined"
},
dataset: {
label: "Dataset",
helpText: "Dataset to bind (measures/dimensions come from its semantic layer)"
label: "Conjunto de datos",
helpText: "Conjunto de datos a vincular (las medidas/dimensiones provienen de su capa semántica)"
},
values: {
label: "Values",
helpText: "Measure names (from the dataset) to display"
label: "Medidas",
helpText: "Nombres de medida (del conjunto de datos) que se mostrarán"
},
rows: {
label: "Rows",
helpText: "Dimension names (from the dataset) to group rows by"
label: "Filas",
helpText: "Nombres de dimensión (del conjunto de datos) por los que agrupar las filas"
},
columns: {
label: "Columnas",
Expand Down Expand Up @@ -1540,7 +1540,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "Granularidad de fecha"
},
measures: {
label: "Measures",
label: "Medidas",
helpText: "Cada medida: nombre, agregación, campo (opcional para count) y formato de visualización/moneda"
},
"measures.name": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export const esESGeneratedSourceHashes: Readonly<Record<string, string>> = {
"metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a",
"metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3",
"metadataForms.dataset.fields.label.label": "c680364257b11569",
"metadataForms.dataset.fields.measures.label": "395dac46abcf1943",
"metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a",
"metadataForms.dataset.fields.name.label": "5274417ff1ccaf01",
"metadataForms.dataset.fields.object.helpText": "09a85579054a7197",
Expand Down Expand Up @@ -195,16 +194,10 @@ export const esESGeneratedSourceHashes: Readonly<Record<string, string>> = {
"metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1",
"metadataForms.page.sections.interface.description": "7e60b977279a05dc",
"metadataForms.page.sections.interface.label": "e2280abc5267ddec",
"metadataForms.report.fields.dataset.helpText": "3cd157f274c985df",
"metadataForms.report.fields.dataset.label": "19647dcb84ce82d3",
"metadataForms.report.fields.drilldown.helpText": "778174eac8057764",
"metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5",
"metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5",
"metadataForms.report.fields.rows.label": "319af74cc41ea823",
"metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40",
"metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa",
"metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1",
"metadataForms.report.fields.values.label": "3edea9880d8fee80",
"metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd",
"metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56",
"metadataForms.seed.description": "c22921feb0f06273",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1398,16 +1398,16 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
helpText: "レポート種別: tabular/summary/matrix/joined"
},
dataset: {
label: "Dataset",
helpText: "Dataset to bind (measures/dimensions come from its semantic layer)"
label: "データセット",
helpText: "バインドするデータセット(メジャー/ディメンションはセマンティックレイヤーから取得)"
},
values: {
label: "Values",
helpText: "Measure names (from the dataset) to display"
label: "メジャー",
helpText: "表示するメジャー名(データセットから取得)"
},
rows: {
label: "Rows",
helpText: "Dimension names (from the dataset) to group rows by"
label: "",
helpText: "行をグループ化するディメンション名(データセットから取得)"
},
columns: {
label: "列",
Expand Down Expand Up @@ -1540,7 +1540,7 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "日付の粒度"
},
measures: {
label: "Measures",
label: "メジャー",
helpText: "各メジャー:名前、集計関数、フィールド(count の場合は省略可)、表示形式/通貨"
},
"measures.name": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export const jaJPGeneratedSourceHashes: Readonly<Record<string, string>> = {
"metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a",
"metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3",
"metadataForms.dataset.fields.label.label": "c680364257b11569",
"metadataForms.dataset.fields.measures.label": "395dac46abcf1943",
"metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a",
"metadataForms.dataset.fields.name.label": "5274417ff1ccaf01",
"metadataForms.dataset.fields.object.helpText": "09a85579054a7197",
Expand Down Expand Up @@ -195,16 +194,10 @@ export const jaJPGeneratedSourceHashes: Readonly<Record<string, string>> = {
"metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1",
"metadataForms.page.sections.interface.description": "7e60b977279a05dc",
"metadataForms.page.sections.interface.label": "e2280abc5267ddec",
"metadataForms.report.fields.dataset.helpText": "3cd157f274c985df",
"metadataForms.report.fields.dataset.label": "19647dcb84ce82d3",
"metadataForms.report.fields.drilldown.helpText": "778174eac8057764",
"metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5",
"metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5",
"metadataForms.report.fields.rows.label": "319af74cc41ea823",
"metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40",
"metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa",
"metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1",
"metadataForms.report.fields.values.label": "3edea9880d8fee80",
"metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd",
"metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56",
"metadataForms.seed.description": "c22921feb0f06273",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
//
// #19355 — the DECISION LEDGER for the report / dataset panel en-echoes.
//
// An en-echo is not automatically a defect: a leaf that reads its English
// source may be an unauthored extractor fill, or it may be the right rendering
// for that locale. The two are byte-identical, so the distinction cannot be
// recovered from the catalog later — it has to be RECORDED when someone looks.
// This file is that record for the four keys #19355 names, one row per string
// leaf, each with the verdict and the reason it was reached.
//
// Why the population here is a LIST while `repeater-row-properties.test.ts`
// DERIVES its own. That file's population is the enumerated repeater row
// properties — 124 of them, measured on this base — and these four keys are
// top-level form fields, outside it. Widening that derivation to all declared
// form-field paths (417, same base) would be red on 230 further echo leaves
// across 82 keys the moment it landed: neighbouring debt nobody has decided
// yet. A pin that lands red is not a pin, so the derived population stays as
// #17508 drew it, and the DECIDED leaves are enumerated here instead. A key
// enters this list when someone judges it, never before.
//
// The instrument that measured the verdicts, with both of its controls, is the
// package's own provenance table `<locale>.source-hashes.generated.ts`: an
// entry exists exactly while the leaf is still a byte copy of the source
// revision. Every leaf this card found echoing carried one in all three
// locales (positive control); `dataset.fields.measures.helpText`, the one leaf
// at these keys that a translator had already written, carried none in any of
// them (negative control). `pnpm i18n:extract` dropped the 21 rows for the
// leaves this card translated, so the table now records them as authored.
//
// ⛔ Do not add a row here to make a red go away. A row is a decision someone
// took about one leaf; the `echo` verdict needs its own per-locale reason
// precisely so that recording "the English is right here" costs a sentence.

import { describe, it, expect } from 'vitest';

import { enMetadataForms } from './en.metadata-forms.generated.js';
import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js';
import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js';
import { esESMetadataForms } from './es-ES.metadata-forms.generated.js';

const TRANSLATED_LOCALES: ReadonlyArray<readonly [string, Record<string, any>]> = [
['zh-CN', zhCNMetadataForms as Record<string, any>],
['ja-JP', jaJPMetadataForms as Record<string, any>],
['es-ES', esESMetadataForms as Record<string, any>],
];

/** `translate` — the echo was an unauthored fill. `echo` — the English IS the rendering. */
type Verdict = 'translate' | 'echo';

interface Decision {
/** Metadata type owning the form panel. */
type: string;
/** The bundle key under `<type>.fields`. */
key: string;
/** Which string leaf of that entry this row decides. */
prop: 'label' | 'helpText';
/**
* The `en` source the verdict was taken against. Held equal to the live
* bundle, so a reworded source reds this file instead of leaving a decision
* standing over text nobody judged.
*/
en: string;
/** The verdict per translated locale. */
verdict: Readonly<Record<string, Verdict>>;
/** Why — recorded for `translate` as much as for `echo`. */
reason: string;
/** Required for every `echo` verdict: why the English is right in THAT locale. */
departures?: Readonly<Record<string, string>>;
}

const ALL_TRANSLATE: Readonly<Record<string, Verdict>> = {
'zh-CN': 'translate',
'ja-JP': 'translate',
'es-ES': 'translate',
};

const SIBLING_PRECEDENT =
'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.';

const DECISIONS: readonly Decision[] = [
{
type: 'report',
key: 'dataset',
prop: 'label',
en: 'Dataset',
verdict: ALL_TRANSLATE,
reason: `${SIBLING_PRECEDENT} Precedent: report.fields['blocks.dataset'].`,
},
{
type: 'report',
key: 'dataset',
prop: 'helpText',
en: 'Dataset to bind (measures/dimensions come from its semantic layer)',
verdict: ALL_TRANSLATE,
reason:
'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.',
},
{
type: 'report',
key: 'values',
prop: 'label',
en: 'Values',
verdict: ALL_TRANSLATE,
reason: `${SIBLING_PRECEDENT} Precedent: report.fields['blocks.values'].`,
},
{
type: 'report',
key: 'values',
prop: 'helpText',
en: 'Measure names (from the dataset) to display',
verdict: ALL_TRANSLATE,
reason:
'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.',
},
{
type: 'report',
key: 'rows',
prop: 'label',
en: 'Rows',
verdict: ALL_TRANSLATE,
reason: `${SIBLING_PRECEDENT} Precedent: report.fields['blocks.rows'], and the adjacent top-level columns is authored on the same panel.`,
},
{
type: 'report',
key: 'rows',
prop: 'helpText',
en: 'Dimension names (from the dataset) to group rows by',
verdict: ALL_TRANSLATE,
reason:
'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.',
},
{
type: 'dataset',
key: 'measures',
prop: 'label',
en: 'Measures',
verdict: ALL_TRANSLATE,
reason:
'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.',
},
{
type: 'dataset',
key: 'measures',
prop: 'helpText',
en: 'Each: name, aggregate, field (optional for count), and display format/currency',
verdict: ALL_TRANSLATE,
reason:
'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.',
},
];

/** The leaf a row points at, in a given catalog. */
function leafOf(forms: Record<string, any>, d: Decision): unknown {
return forms[d.type]?.fields?.[d.key]?.[d.prop];
}

describe('#19355 — the ledger itself (controls before verdicts)', () => {
it('decides every string leaf of the four keys the card names, and nothing else', () => {
// Lit — the ledger is the size it claims: four keys, eight leaves, three
// locales, 24 decisions.
expect(DECISIONS.length).toBe(8);
expect(new Set(DECISIONS.map((d) => `${d.type}.fields.${d.key}`))).toEqual(
new Set(['report.fields.values', 'report.fields.rows', 'report.fields.dataset', 'dataset.fields.measures']),
);
expect(DECISIONS.flatMap((d) => Object.keys(d.verdict)).length).toBe(24);
for (const d of DECISIONS) {
expect(Object.keys(d.verdict).sort(), `${d.type}.${d.key}.${d.prop} names every translated locale`).toEqual([
'es-ES',
'ja-JP',
'zh-CN',
]);
}
});

it('every row is pinned to the live `en` source it was decided against', () => {
for (const d of DECISIONS) {
expect(
(enMetadataForms as Record<string, any>)[d.type]?.fields?.[d.key]?.[d.prop],
`en ${d.type}.fields.${d.key}.${d.prop} moved — re-judge the decision, do not refresh this row`,
).toBe(d.en);
}
});

it('the echo predicate can say "echo" — fed the `en` catalog, it flags every row', () => {
// Dark. `translated !== en` is the whole verdict test below; run it against
// the source catalog itself and it must fail for all 8 rows, or a green
// verdict run means nothing.
const flagged = DECISIONS.filter((d) => leafOf(enMetadataForms as Record<string, any>, d) === d.en);
expect(flagged.length).toBe(DECISIONS.length);
});

it('every decision records a reason, and every `echo` records its per-locale departure', () => {
for (const d of DECISIONS) {
expect(d.reason.length, `${d.type}.fields.${d.key}.${d.prop} records no reason`).toBeGreaterThan(40);
for (const [locale, verdict] of Object.entries(d.verdict)) {
if (verdict !== 'echo') continue;
const departure = d.departures?.[locale] ?? '';
expect(
departure.length,
`${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`,
).toBeGreaterThan(40);
}
}
});
});

describe('#19355 — the catalogs hold what the ledger decided', () => {
for (const [locale, forms] of TRANSLATED_LOCALES) {
it(`${locale}: every decided leaf matches its verdict`, () => {
for (const d of DECISIONS) {
const id = `${locale} ${d.type}.fields.${d.key}.${d.prop}`;
const value = leafOf(forms, d);
expect(typeof value, `${id} is missing from the catalog`).toBe('string');
expect((value as string).length, `${id} is empty`).toBeGreaterThan(0);
if (d.verdict[locale] === 'translate') {
expect(value, `${id} reads its en source again — the decision was that this echo is a fill`).not.toBe(d.en);
} else {
expect(value, `${id} is a declared echo and must stay the en source`).toBe(d.en);
}
}
});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -1398,16 +1398,16 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
helpText: "报表类型:tabular / summary / matrix / joined"
},
dataset: {
label: "Dataset",
helpText: "Dataset to bind (measures/dimensions come from its semantic layer)"
label: "数据集",
helpText: "要绑定的数据集(度量/维度来自其语义层)"
},
values: {
label: "Values",
helpText: "Measure names (from the dataset) to display"
label: "度量",
helpText: "要展示的度量名(来自该数据集)"
},
rows: {
label: "Rows",
helpText: "Dimension names (from the dataset) to group rows by"
label: "",
helpText: "用于分组行的维度名(来自该数据集)"
},
columns: {
label: "列",
Expand Down Expand Up @@ -1540,7 +1540,7 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "日期粒度"
},
measures: {
label: "Measures",
label: "度量",
helpText: "每个度量:名称、聚合函数、字段(count 时可省略)、显示格式/货币"
},
"measures.name": {
Expand Down
Loading
Loading