diff --git a/.changeset/olive-moons-tickle.md b/.changeset/olive-moons-tickle.md new file mode 100644 index 00000000000..e225d5eab2f --- /dev/null +++ b/.changeset/olive-moons-tickle.md @@ -0,0 +1,7 @@ +--- +'@objectstack/platform-objects': patch +--- + +Translate the object field-editor panel leaves that shipped their English source in every locale + +Fourteen metadata-form keys under `object.fields['fields.*']` — the field editor on the object form (`placeholder`, `valueDomain`, `rows`, `lookupFilters`, `deleteBehavior`, `expression`, the four `summaryOperations` entries, `autonumberFormat`, `visibleWhen`, `readonlyWhen`, `requiredWhen`) — carried both their `label` and their `helpText` byte-identical to the `en` source in `zh-CN`, `ja-JP` and `es-ES`, so an author working in a translated locale read English on the most trafficked authoring panel in Studio while everything around them was translated. All 28 leaves are now translated in each locale; each was judged individually, and the 84 verdicts with their reasons are pinned in `object-field-editor-panel-echo-decisions.test.ts`, which also derives the panel's population so a re-fill or a newly added field is red on the day it lands. No key was added, removed or renamed — the bundles' shape is unchanged. diff --git a/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts index bfef86d9c7c..375297e93fa 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts @@ -109,8 +109,8 @@ export const esESMetadataForms: NonNullable = helpText: "Valor predeterminado para registros nuevos (literal JSON)" }, "fields.placeholder": { - label: "Placeholder", - helpText: "Hint text shown inside the empty input; disappears once a value is entered" + label: "Marcador de posición", + helpText: "Texto de ayuda que se muestra dentro del campo vacío; desaparece al introducir un valor" }, "fields.maxLength": { label: "Longitud máxima", @@ -121,12 +121,12 @@ export const esESMetadataForms: NonNullable = helpText: "Mínimo de caracteres" }, "fields.valueDomain": { - label: "Value Domain", - helpText: "Standard the written value must belong to; a write carrying a non-member is refused" + label: "Dominio de valores", + helpText: "Estándar al que debe pertenecer el valor escrito; se rechaza una escritura con un valor que no pertenezca" }, "fields.rows": { - label: "Rows", - helpText: "Inline editor height (text rows)" + label: "Filas", + helpText: "Altura del editor en línea (filas de texto)" }, "fields.min": { label: "Mínimo", @@ -165,44 +165,44 @@ export const esESMetadataForms: NonNullable = helpText: "Nombre del objeto de destino. En un campo tree es opcional y, si se indica, debe ser este mismo objeto (un árbol es una jerarquía dentro de su propio objeto — para enlazar con otro objeto, usa un lookup)" }, "fields.lookupFilters": { - label: "Lookup Filters", - helpText: "Filter rules applied to the picker ({field, operator, value})" + label: "Filtros de lookup", + helpText: "Reglas de filtro aplicadas al selector ({field, operator, value})" }, "fields.deleteBehavior": { - label: "Delete Behavior", - helpText: "What happens when the referenced record is deleted" + label: "Comportamiento al eliminar", + helpText: "Qué ocurre cuando se elimina el registro referenciado" }, "fields.multiple": { label: "Selección múltiple", helpText: "Permitir seleccionar varios registros" }, "fields.expression": { - label: "Expression", - helpText: "CEL formula expression" + label: "Expresión", + helpText: "Expresión de fórmula CEL" }, "fields.returnType": { label: "Tipo de retorno", helpText: "Tipo de resultado para fórmulas" }, "fields.summaryOperations": { - label: "Summary Operations", - helpText: "Roll-up: which child object, which field, which aggregation" + label: "Operaciones de resumen", + helpText: "Roll-up: qué objeto hijo, qué campo y qué agregación" }, "fields.summaryOperations.object": { - label: "Object", - helpText: "Source child object name" + label: "Objeto", + helpText: "Nombre del objeto hijo de origen" }, "fields.summaryOperations.field": { - label: "Field", - helpText: "Field on the child object to aggregate (ignored for count)" + label: "Campo", + helpText: "Campo del objeto hijo que se agrega (se ignora para count)" }, "fields.summaryOperations.function": { - label: "Function", - helpText: "Aggregation function" + label: "Función", + helpText: "Función de agregación" }, "fields.autonumberFormat": { - label: "Autonumber Format", - helpText: "e.g. \"INV-{0000}\"; date tokens {YYYY}/{MM}/{DD} and {field_name} interpolation supported" + label: "Formato de autonumeración", + helpText: "p. ej. \"INV-{0000}\"; admite tokens de fecha {YYYY}/{MM}/{DD} e interpolación de {field_name}" }, "fields.language": { label: "Idioma", @@ -213,16 +213,16 @@ export const esESMetadataForms: NonNullable = helpText: "Resume este campo en la cronología de actividad del registro" }, "fields.visibleWhen": { - label: "Visible When", - helpText: "CEL predicate — field is shown only when TRUE" + label: "Condición de visibilidad", + helpText: "Predicado CEL: el campo solo se muestra cuando es TRUE" }, "fields.readonlyWhen": { - label: "Readonly When", - helpText: "CEL predicate — field is read-only when TRUE (enforced server-side)" + label: "Condición de solo lectura", + helpText: "Predicado CEL: el campo es de solo lectura cuando es TRUE (aplicado en el servidor)" }, "fields.requiredWhen": { - label: "Required When", - helpText: "CEL predicate — field is required when TRUE (enforced server-side)" + label: "Condición de obligatoriedad", + helpText: "Predicado CEL: el campo es obligatorio cuando es TRUE (aplicado en el servidor)" }, enable: { label: "Enable", diff --git a/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts index d9b9b79f661..5e9da982890 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts @@ -102,34 +102,6 @@ export const esESGeneratedSourceHashes: Readonly> = { "metadataForms.object.fields.enable.label": "fdecf828d4e86eed", "metadataForms.object.fields.enable.searchable.label": "d8f50099e7217830", "metadataForms.object.fields.enable.trackHistory.label": "7bc28212817b2c43", - "metadataForms.object.fields.fields.autonumberFormat.helpText": "14efe56f7cf8812e", - "metadataForms.object.fields.fields.autonumberFormat.label": "ced754e3dde1e78f", - "metadataForms.object.fields.fields.deleteBehavior.helpText": "6d94538a80df113e", - "metadataForms.object.fields.fields.deleteBehavior.label": "01c3c66572ce674b", - "metadataForms.object.fields.fields.expression.helpText": "6c559ad697dbaa85", - "metadataForms.object.fields.fields.expression.label": "cd1b52ad1588075e", - "metadataForms.object.fields.fields.lookupFilters.helpText": "ce8004df6ef768f1", - "metadataForms.object.fields.fields.lookupFilters.label": "c2860cdcfe74ad9a", - "metadataForms.object.fields.fields.placeholder.helpText": "497ea6299e075744", - "metadataForms.object.fields.fields.placeholder.label": "44d62b55b63fe718", - "metadataForms.object.fields.fields.readonlyWhen.helpText": "1daba886fd4e7f25", - "metadataForms.object.fields.fields.readonlyWhen.label": "af1c3f486fb2fbf7", - "metadataForms.object.fields.fields.requiredWhen.helpText": "997a0d21e5e32203", - "metadataForms.object.fields.fields.requiredWhen.label": "89b4d31ea46e2e76", - "metadataForms.object.fields.fields.rows.helpText": "3adb8c4c447f36b7", - "metadataForms.object.fields.fields.rows.label": "319af74cc41ea823", - "metadataForms.object.fields.fields.summaryOperations.field.helpText": "ff2f535b8f9cfffe", - "metadataForms.object.fields.fields.summaryOperations.field.label": "e21c314685cd95ca", - "metadataForms.object.fields.fields.summaryOperations.function.helpText": "b1022db50ca584b3", - "metadataForms.object.fields.fields.summaryOperations.function.label": "9ae3f998b780a051", - "metadataForms.object.fields.fields.summaryOperations.helpText": "4657cc4f93ecc549", - "metadataForms.object.fields.fields.summaryOperations.label": "bcf65520573738ee", - "metadataForms.object.fields.fields.summaryOperations.object.helpText": "7e6fc21663360844", - "metadataForms.object.fields.fields.summaryOperations.object.label": "d0c49032d95ebb36", - "metadataForms.object.fields.fields.valueDomain.helpText": "649e2e89dfeb2e8a", - "metadataForms.object.fields.fields.valueDomain.label": "18ebeb7e56e792a1", - "metadataForms.object.fields.fields.visibleWhen.helpText": "46de2d3ff57b6667", - "metadataForms.object.fields.fields.visibleWhen.label": "c852d4249db93285", "metadataForms.object.fields.lifecycle.archive.after.helpText": "e0e76ef140528e1c", "metadataForms.object.fields.lifecycle.archive.after.label": "05e119362ddb98d6", "metadataForms.object.fields.lifecycle.archive.helpText": "e1d3f49d818d0807", diff --git a/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts index 0fd43e68a36..44fc078cfab 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts @@ -109,8 +109,8 @@ export const jaJPMetadataForms: NonNullable = helpText: "新規レコードの既定値(JSON リテラル)" }, "fields.placeholder": { - label: "Placeholder", - helpText: "Hint text shown inside the empty input; disappears once a value is entered" + label: "プレースホルダー", + helpText: "空の入力欄の内側に表示されるヒント文言。値を入力すると消えます" }, "fields.maxLength": { label: "最大長", @@ -121,12 +121,12 @@ export const jaJPMetadataForms: NonNullable = helpText: "最小文字数" }, "fields.valueDomain": { - label: "Value Domain", - helpText: "Standard the written value must belong to; a write carrying a non-member is refused" + label: "値ドメイン", + helpText: "書き込む値が属していなければならない標準。メンバー以外の値を含む書き込みは拒否されます" }, "fields.rows": { - label: "Rows", - helpText: "Inline editor height (text rows)" + label: "行数", + helpText: "インライン編集欄の高さ(テキスト行数)" }, "fields.min": { label: "最小値", @@ -165,44 +165,44 @@ export const jaJPMetadataForms: NonNullable = helpText: "対象オブジェクト名。tree フィールドでは省略可能で、指定する場合はこのオブジェクト自身でなければなりません(ツリーは同一オブジェクト内の階層です — 別のオブジェクトへリンクする場合は lookup を使います)" }, "fields.lookupFilters": { - label: "Lookup Filters", - helpText: "Filter rules applied to the picker ({field, operator, value})" + label: "Lookup フィルター", + helpText: "ピッカーに適用されるフィルタールール({field, operator, value})" }, "fields.deleteBehavior": { - label: "Delete Behavior", - helpText: "What happens when the referenced record is deleted" + label: "削除動作", + helpText: "参照先レコード削除時の動作" }, "fields.multiple": { label: "複数選択", helpText: "複数レコードの選択を許可" }, "fields.expression": { - label: "Expression", - helpText: "CEL formula expression" + label: "式", + helpText: "CEL 数式" }, "fields.returnType": { label: "戻り値の型", helpText: "公式の結果型" }, "fields.summaryOperations": { - label: "Summary Operations", - helpText: "Roll-up: which child object, which field, which aggregation" + label: "集計操作", + helpText: "ロールアップ:どの子オブジェクトの、どのフィールドを、どの集計関数で" }, "fields.summaryOperations.object": { - label: "Object", - helpText: "Source child object name" + label: "オブジェクト", + helpText: "集計元の子オブジェクト名" }, "fields.summaryOperations.field": { - label: "Field", - helpText: "Field on the child object to aggregate (ignored for count)" + label: "フィールド", + helpText: "集計対象となる子オブジェクトのフィールド(count では無視)" }, "fields.summaryOperations.function": { - label: "Function", - helpText: "Aggregation function" + label: "関数", + helpText: "集計関数" }, "fields.autonumberFormat": { - label: "Autonumber Format", - helpText: "e.g. \"INV-{0000}\"; date tokens {YYYY}/{MM}/{DD} and {field_name} interpolation supported" + label: "自動採番フォーマット", + helpText: "例: \"INV-{0000}\"。日付トークン {YYYY}/{MM}/{DD} と {field_name} の補間に対応" }, "fields.language": { label: "言語", @@ -213,16 +213,16 @@ export const jaJPMetadataForms: NonNullable = helpText: "レコードのアクティビティタイムラインでこのフィールドを要約する" }, "fields.visibleWhen": { - label: "Visible When", - helpText: "CEL predicate — field is shown only when TRUE" + label: "表示条件", + helpText: "CEL 述語 — TRUE の場合のみフィールドを表示" }, "fields.readonlyWhen": { - label: "Readonly When", - helpText: "CEL predicate — field is read-only when TRUE (enforced server-side)" + label: "読み取り専用条件", + helpText: "CEL 述語 — TRUE の場合フィールドは読み取り専用(サーバー側で強制)" }, "fields.requiredWhen": { - label: "Required When", - helpText: "CEL predicate — field is required when TRUE (enforced server-side)" + label: "必須条件", + helpText: "CEL 述語 — TRUE の場合フィールドは必須(サーバー側で強制)" }, enable: { label: "Enable", diff --git a/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts index 0f0cea2d343..a777514168b 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts @@ -102,34 +102,6 @@ export const jaJPGeneratedSourceHashes: Readonly> = { "metadataForms.object.fields.enable.label": "fdecf828d4e86eed", "metadataForms.object.fields.enable.searchable.label": "d8f50099e7217830", "metadataForms.object.fields.enable.trackHistory.label": "7bc28212817b2c43", - "metadataForms.object.fields.fields.autonumberFormat.helpText": "14efe56f7cf8812e", - "metadataForms.object.fields.fields.autonumberFormat.label": "ced754e3dde1e78f", - "metadataForms.object.fields.fields.deleteBehavior.helpText": "6d94538a80df113e", - "metadataForms.object.fields.fields.deleteBehavior.label": "01c3c66572ce674b", - "metadataForms.object.fields.fields.expression.helpText": "6c559ad697dbaa85", - "metadataForms.object.fields.fields.expression.label": "cd1b52ad1588075e", - "metadataForms.object.fields.fields.lookupFilters.helpText": "ce8004df6ef768f1", - "metadataForms.object.fields.fields.lookupFilters.label": "c2860cdcfe74ad9a", - "metadataForms.object.fields.fields.placeholder.helpText": "497ea6299e075744", - "metadataForms.object.fields.fields.placeholder.label": "44d62b55b63fe718", - "metadataForms.object.fields.fields.readonlyWhen.helpText": "1daba886fd4e7f25", - "metadataForms.object.fields.fields.readonlyWhen.label": "af1c3f486fb2fbf7", - "metadataForms.object.fields.fields.requiredWhen.helpText": "997a0d21e5e32203", - "metadataForms.object.fields.fields.requiredWhen.label": "89b4d31ea46e2e76", - "metadataForms.object.fields.fields.rows.helpText": "3adb8c4c447f36b7", - "metadataForms.object.fields.fields.rows.label": "319af74cc41ea823", - "metadataForms.object.fields.fields.summaryOperations.field.helpText": "ff2f535b8f9cfffe", - "metadataForms.object.fields.fields.summaryOperations.field.label": "e21c314685cd95ca", - "metadataForms.object.fields.fields.summaryOperations.function.helpText": "b1022db50ca584b3", - "metadataForms.object.fields.fields.summaryOperations.function.label": "9ae3f998b780a051", - "metadataForms.object.fields.fields.summaryOperations.helpText": "4657cc4f93ecc549", - "metadataForms.object.fields.fields.summaryOperations.label": "bcf65520573738ee", - "metadataForms.object.fields.fields.summaryOperations.object.helpText": "7e6fc21663360844", - "metadataForms.object.fields.fields.summaryOperations.object.label": "d0c49032d95ebb36", - "metadataForms.object.fields.fields.valueDomain.helpText": "649e2e89dfeb2e8a", - "metadataForms.object.fields.fields.valueDomain.label": "18ebeb7e56e792a1", - "metadataForms.object.fields.fields.visibleWhen.helpText": "46de2d3ff57b6667", - "metadataForms.object.fields.fields.visibleWhen.label": "c852d4249db93285", "metadataForms.object.fields.lifecycle.archive.after.helpText": "e0e76ef140528e1c", "metadataForms.object.fields.lifecycle.archive.after.label": "05e119362ddb98d6", "metadataForms.object.fields.lifecycle.archive.helpText": "e1d3f49d818d0807", diff --git a/packages/platform-objects/src/apps/translations/object-field-editor-panel-echo-decisions.test.ts b/packages/platform-objects/src/apps/translations/object-field-editor-panel-echo-decisions.test.ts new file mode 100644 index 00000000000..610d1fabd30 --- /dev/null +++ b/packages/platform-objects/src/apps/translations/object-field-editor-panel-echo-decisions.test.ts @@ -0,0 +1,551 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// #19403 — the DECISION LEDGER for the OBJECT FIELD-EDITOR 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 ONE panel family, in the shape #19355 landed in +// `report-dataset-panel-echo-decisions.test.ts`: one row per string leaf, each +// carrying its verdict per locale, the reason it was reached, and the `en` +// source it was judged against. +// +// ## The family, and why the card is not taken in one sweep +// +// The card's population is the `.label` leaves of the metadata-form catalogs; +// 79 of the 538 read their `en` source in all three locales on this base. Those +// 79 are not one card's worth of judgement, so this round takes one family — +// the object field editor, `object.fields['fields.*']`, the 14 keys the card +// samples (placeholder, valueDomain, rows, deleteBehavior, expression …) — and +// decides every string leaf of those keys, `helpText` included: a panel whose +// field name is Chinese and whose tooltip is English is the same defect half +// fixed. +// +// ⇒ decided here: 14 keys × 2 string leaves × 3 locales = 84 decisions. +// +// ## The instrument, with both of its controls +// +// Census: flatten every `*.metadata-forms.generated.ts` bundle, compare each +// `.label` leaf against `en`. On base 0862063ba, before this change: +// +// en `.label` leaves 538 +// POSITIVE CONTROL — labels genuinely translated 459 (zh-CN) · 443 · 443 +// labels echoing `en` 79 (zh-CN) · 95 · 95 +// label keys echoing in ALL THREE locales 79 ⇒ 237 leaves +// +// The control is what makes the echo count mean something: 459 translated +// against 79 echoing is a discrimination, not a parser that matches +// everything. +// +// Per-leaf control: this package's provenance table +// (`.source-hashes.generated.ts`) holds an entry exactly while a leaf +// is still a byte copy of the source revision. Every one of the 28 leaves +// decided here carried one in all three locales; every AUTHORED leaf on the +// same panel (label, type, required, defaultValue, maxLength, …) carried none +// in any of them. `pnpm i18n:extract` dropped the 84 rows for the leaves this +// round translated, so the table now records them as authored. +// +// ## Why no pin saw this panel — measured, not assumed +// +// `repeater-row-properties.test.ts` derives its population from the forms and +// already refuses an en-echo inside it. It filters on `spec.type === +// 'repeater'`, and the object form declares its field editor as +// `{ field: 'fields', type: 'record' }` with 35 children — so that derivation +// yields exactly four row properties for `objectForm` (`fields.options.label` +// / `.value` / `.color` / `.description`) and none of the 14 keys here. The +// derived pin at the foot of this file closes that gap for this panel: it +// walks the `object.fields['fields.*']` leaves out of the `en` catalog, so a +// key added to the field editor tomorrow, or a re-fill of a decided one, is +// red on the day it lands rather than a card later. +// +// ⛔ 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]> = [ + ['zh-CN', zhCNMetadataForms as Record], + ['ja-JP', jaJPMetadataForms as Record], + ['es-ES', esESMetadataForms as Record], +]; + +/** `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 `.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>; + /** 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>; +} + +const ALL_TRANSLATE: Readonly> = { + 'zh-CN': 'translate', + 'ja-JP': 'translate', + 'es-ES': 'translate', +}; + +/** + * The panel-internal control, cited by the rows whose evidence IS the panel: + * the leaf held a provenance entry in all three locales while its authored + * neighbours held none. + */ +const PANEL_CONTROL = + 'Prose, not a term of art. Its neighbours on this same panel (defaultValue, maxLength, searchable, sortable, returnType) are authored in all three locales, and this leaf carried a provenance-table entry in all three while none of them did — which is what an unauthored fill looks like here.'; + +const DECISIONS: readonly Decision[] = [ + { + type: 'object', + key: 'fields.placeholder', + prop: 'label', + en: 'Placeholder', + verdict: ALL_TRANSLATE, + reason: + "This catalog's own authored answer for this exact English word is one panel away: action.fields.params.placeholder.label reads 占位文本 / プレースホルダー / Marcador de posición. A translator who judged the English right here would have had to judge it wrong there. A fill.", + }, + { + type: 'object', + key: 'fields.placeholder', + prop: 'helpText', + en: 'Hint text shown inside the empty input; disappears once a value is entered', + verdict: ALL_TRANSLATE, + reason: PANEL_CONTROL, + }, + { + type: 'object', + key: 'fields.valueDomain', + prop: 'label', + en: 'Value Domain', + verdict: ALL_TRANSLATE, + reason: + 'A term of art with a settled rendering in each locale (值域 / 値ドメイン / Dominio de valores), and this panel translates its other constrained-value labels — maskingRule and returnType are authored — rather than keeping them English. The sibling field panel echoes the same key, which is the extractor default reaching both panels, not the same judgement taken twice.', + }, + { + type: 'object', + key: 'fields.valueDomain', + prop: 'helpText', + en: 'Standard the written value must belong to; a write carrying a non-member is refused', + verdict: ALL_TRANSLATE, + reason: + 'Prose stating a refusal rule — exactly the kind of sentence an author needs in their own language — and a byte copy in all three locales while the adjacent maskingRule.helpText, a longer sentence with embedded literals, is authored in all three.', + }, + { + type: 'object', + key: 'fields.rows', + prop: 'label', + en: 'Rows', + verdict: ALL_TRANSLATE, + reason: + 'Decided AGAINST the same-string precedent, which is why it needed judging: report.fields.rows.label is authored as 行 / 行 / Filas, but that leaf names a pivot axis while this one is a COUNT of text rows (its own helpText says so). zh-CN and ja-JP therefore depart to 行数; es-ES keeps Filas, which carries both senses.', + }, + { + type: 'object', + key: 'fields.rows', + prop: 'helpText', + en: 'Inline editor height (text rows)', + verdict: ALL_TRANSLATE, + reason: + 'Prose, and the leaf that settles the label above it: it is what tells a reader this Rows is a height, not an axis. Left English it would leave the one disambiguating sentence on the panel unreadable in three locales.', + }, + { + type: 'object', + key: 'fields.lookupFilters', + prop: 'label', + en: 'Lookup Filters', + verdict: ALL_TRANSLATE, + reason: + 'Rendered with the machine token kept: `lookup` is a field TYPE value here, and the authored zh-CN/ja-JP/es-ES text of fields.reference.helpText keeps both `lookup` and `tree` in English inside otherwise translated prose. Only the common noun is rendered, with this catalog\'s settled word for Filter (筛选 / フィルター / Filtro, from view.fields.filter and dashboard.fields.widgets.filter).', + }, + { + type: 'object', + key: 'fields.lookupFilters', + prop: 'helpText', + en: 'Filter rules applied to the picker ({field, operator, value})', + verdict: ALL_TRANSLATE, + reason: + 'Prose wrapped around an authorable literal. The literal ({field, operator, value}) is the shape the author must type and stays byte-identical; the sentence around it is rendered — the same split the authored maskingRule.helpText makes for its preset literals.', + }, + { + type: 'object', + key: 'fields.deleteBehavior', + prop: 'label', + en: 'Delete Behavior', + verdict: ALL_TRANSLATE, + reason: + 'The sharpest case on this panel: the SAME concept is authored one metadata type across — field.fields.deleteBehavior.label reads 删除行为 / 削除動作 / Comportamiento al eliminar — while the object panel\'s copy stayed English. One concept cannot be a decision on one panel and the right rendering on the other.', + }, + { + type: 'object', + key: 'fields.deleteBehavior', + prop: 'helpText', + en: 'What happens when the referenced record is deleted', + verdict: ALL_TRANSLATE, + reason: + 'Same evidence as its label: field.fields.deleteBehavior.helpText carries the authored sentence for a near-identical English source in all three locales (被引用记录删除时的处理方式 / 参照先レコード削除時の動作 / Qué ocurre cuando se elimina el registro referenciado).', + }, + { + type: 'object', + key: 'fields.expression', + prop: 'label', + en: 'Expression', + verdict: ALL_TRANSLATE, + reason: + 'Authored at two other paths in this package for the identical English word: field.fields.expression.label and objects.sys_job.fields.schedule_expression.label, both 表达式 / 式 / Expresión in all three locales.', + }, + { + type: 'object', + key: 'fields.expression', + prop: 'helpText', + en: 'CEL formula expression', + verdict: ALL_TRANSLATE, + reason: + 'CEL is a language name and stays verbatim; the rest is prose. The formula vocabulary already exists on this very panel — returnType.helpText ("Result type for formulas") is authored in all three locales — so this leaf was simply never reached.', + }, + { + type: 'object', + key: 'fields.summaryOperations', + prop: 'label', + en: 'Summary Operations', + verdict: ALL_TRANSLATE, + reason: + 'field.fields.summaryOperations.label authors the same container under the same name in all three locales (汇总操作 / 集計操作 / Operaciones de resumen). The object panel holds the identical concept and stayed English.', + }, + { + type: 'object', + key: 'fields.summaryOperations', + prop: 'helpText', + en: 'Roll-up: which child object, which field, which aggregation', + verdict: ALL_TRANSLATE, + reason: + 'Prose naming three vocabularies (child object, field, aggregation) that this catalog already renders elsewhere, and its sibling field.fields.summaryOperations.helpText is authored for the same roll-up sentence — es-ES keeps "roll-up" exactly as that sibling does.', + }, + { + type: 'object', + key: 'fields.summaryOperations.object', + prop: 'label', + en: 'Object', + verdict: ALL_TRANSLATE, + reason: + 'The container above it was authored on the sibling panel while its three row properties were not — the fill pattern, key for key. 对象 / オブジェクト / Objeto is what this catalog calls Object at nine other paths (object.label, hook.fields.object, page.fields.object, dashboard.fields.globalFilters.object, …).', + }, + { + type: 'object', + key: 'fields.summaryOperations.object', + prop: 'helpText', + en: 'Source child object name', + verdict: ALL_TRANSLATE, + reason: + 'Four words of prose, no machine token, and the neighbouring reference.helpText — a far longer sentence about the same relationship — is authored in all three locales.', + }, + { + type: 'object', + key: 'fields.summaryOperations.field', + prop: 'label', + en: 'Field', + verdict: ALL_TRANSLATE, + reason: + '字段 / フィールド / Campo is this catalog\'s authored answer for the bare word Field at seven other paths (field.label, page.fields.interfaceConfig.sort.field, dataset.fields.measures.field, action.fields.params.field, …). Only the roll-up row properties kept the English.', + }, + { + type: 'object', + key: 'fields.summaryOperations.field', + prop: 'helpText', + en: 'Field on the child object to aggregate (ignored for count)', + verdict: ALL_TRANSLATE, + reason: + 'Prose with one machine value inside it. `count` is an aggregation value the author writes, so it stays English, following this catalog\'s own token discipline in the authored fields.reference.helpText; the sentence around it is rendered.', + }, + { + type: 'object', + key: 'fields.summaryOperations.function', + prop: 'label', + en: 'Function', + verdict: ALL_TRANSLATE, + reason: + 'Rendered as the plain word (函数 / 関数 / Función) and deliberately NOT as "aggregation function": the source says Function at the label and Aggregation function at the helpText one line below, and collapsing the two would translate away a distinction the source draws.', + }, + { + type: 'object', + key: 'fields.summaryOperations.function', + prop: 'helpText', + en: 'Aggregation function', + verdict: ALL_TRANSLATE, + reason: + 'Two words, and the term this catalog already uses for aggregation in the authored sibling roll-up sentence (聚合 / 集計 / agregación). Left English it would be the only untranslated tooltip in a panel whose every other tooltip is authored.', + }, + { + type: 'object', + key: 'fields.autonumberFormat', + prop: 'label', + en: 'Autonumber Format', + verdict: ALL_TRANSLATE, + reason: + 'A compound of two ordinary words: Format is authored as 格式 / フォーマット / Formato at three other paths (dataset.fields.measures.format, sys_saved_report.format, sys_report_schedule.format), and this panel authors its other format-shaped label, maskingRule, rather than keeping it English.', + }, + { + type: 'object', + key: 'fields.autonumberFormat', + prop: 'helpText', + en: 'e.g. "INV-{0000}"; date tokens {YYYY}/{MM}/{DD} and {field_name} interpolation supported', + verdict: ALL_TRANSLATE, + reason: + 'Prose around authorable literals. The tokens ("INV-{0000}", {YYYY}/{MM}/{DD}, {field_name}) are the syntax the author types and stay byte-identical in every locale; only the sentence that explains them is rendered.', + }, + { + type: 'object', + key: 'fields.visibleWhen', + prop: 'label', + en: 'Visible When', + verdict: ALL_TRANSLATE, + reason: + 'The phrase already has an authored answer in this catalog, twice: field.fields.options.visibleWhen.label and action.fields.params.visible.label both read 可见条件 / 表示条件 / Condición de visibilidad.', + }, + { + type: 'object', + key: 'fields.visibleWhen', + prop: 'helpText', + en: 'CEL predicate — field is shown only when TRUE', + verdict: ALL_TRANSLATE, + reason: + 'The catalog\'s authored CEL sentences (action.fields.params.visible / disabled helpText) supply the pattern. `predicate` is kept distinct from `expression` — 判定式 / 述語 / predicado — because this panel uses both words two entries apart, one for a boolean and one for a formula.', + }, + { + type: 'object', + key: 'fields.readonlyWhen', + prop: 'label', + en: 'Readonly When', + verdict: ALL_TRANSLATE, + reason: + 'Composed from two halves this catalog has already authored: readonly is 只读 / 読み取り専用 / Solo lectura on this same panel, and the "… When" suffix has its rendering at field.fields.options.visibleWhen. Nothing here needed inventing, which is why leaving it English was never a decision.', + }, + { + type: 'object', + key: 'fields.readonlyWhen', + prop: 'helpText', + en: 'CEL predicate — field is read-only when TRUE (enforced server-side)', + verdict: ALL_TRANSLATE, + reason: + 'Same pattern as visibleWhen.helpText, and the parenthetical matters most to the reader who cannot read it: "(enforced server-side)" is the sentence that says this is not a UI-only hint. This panel renders its other parenthetical qualifiers in all three locales.', + }, + { + type: 'object', + key: 'fields.requiredWhen', + prop: 'label', + en: 'Required When', + verdict: ALL_TRANSLATE, + reason: + 'Composed from two halves this catalog has already authored: required is 必填 / 必須 / Obligatorio on this same panel, and the "… When" suffix has its rendering at field.fields.options.visibleWhen.', + }, + { + type: 'object', + key: 'fields.requiredWhen', + prop: 'helpText', + en: 'CEL predicate — field is required when TRUE (enforced server-side)', + verdict: ALL_TRANSLATE, + reason: + 'Same pattern as its readonly twin, and the same reason for the parenthetical: a required-when rule the server enforces is a validation contract, not a hint, and the sentence saying so was English in all three locales.', + }, +]; + +/** The leaf a row points at, in a given catalog. */ +function leafOf(forms: Record, d: Decision): unknown { + return forms[d.type]?.fields?.[d.key]?.[d.prop]; +} + +/** + * The ledger's own refusal, as a predicate so it can be PROVED capable of + * firing. Every verdict in this round is `translate`, so asserting the rule + * over `DECISIONS` alone would be a check that evaluates nothing. + */ +function undeclaredEchoes(rows: readonly Decision[]): string[] { + const bad: string[] = []; + for (const d of rows) { + for (const [locale, verdict] of Object.entries(d.verdict)) { + if (verdict !== 'echo') continue; + if ((d.departures?.[locale] ?? '').length <= 40) bad.push(`${locale} ${d.type}.fields.${d.key}.${d.prop}`); + } + } + return bad; +} + +/** Every string leaf of the object field-editor panel, derived from the `en` catalog. */ +function panelLeaves(): Array<{ key: string; prop: string; en: string }> { + const out: Array<{ key: string; prop: string; en: string }> = []; + const fields = (enMetadataForms as Record).object?.fields ?? {}; + for (const [key, entry] of Object.entries(fields)) { + if (!key.startsWith('fields.') || !entry || typeof entry !== 'object') continue; + for (const [prop, value] of Object.entries(entry as Record)) { + if (typeof value === 'string') out.push({ key, prop, en: value }); + } + } + return out; +} + +const PANEL_LEAVES = panelLeaves(); + +describe('#19403 — the ledger itself (controls before verdicts)', () => { + it('decides every string leaf of the 14 keys this round takes, and nothing else', () => { + // Lit — the ledger is the size it claims: 14 keys, 28 leaves, three + // locales, 84 decisions. + expect(DECISIONS.length).toBe(28); + expect(new Set(DECISIONS.map((d) => d.key)).size).toBe(14); + expect(DECISIONS.flatMap((d) => Object.keys(d.verdict)).length).toBe(84); + for (const d of DECISIONS) { + expect(d.type, 'this round is the object field-editor panel only').toBe('object'); + expect(d.key.startsWith('fields.'), `${d.key} is outside this panel`).toBe(true); + expect(Object.keys(d.verdict).sort(), `${d.key}.${d.prop} names every translated locale`).toEqual([ + 'es-ES', + 'ja-JP', + 'zh-CN', + ]); + } + // Every decided key is decided on BOTH of its string leaves — a panel + // whose field name is translated and whose tooltip is not is the same + // defect half fixed. + for (const key of new Set(DECISIONS.map((d) => d.key))) { + expect( + DECISIONS.filter((d) => d.key === key) + .map((d) => d.prop) + .sort(), + `${key} is decided on one leaf only`, + ).toEqual(['helpText', 'label']); + } + }); + + it('every row is pinned to the live `en` source it was decided against', () => { + for (const d of DECISIONS) { + expect( + (enMetadataForms as Record)[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 28 rows, or a green + // verdict run means nothing. + const flagged = DECISIONS.filter((d) => leafOf(enMetadataForms as Record, d) === d.en); + expect(flagged.length).toBe(DECISIONS.length); + }); + + it('every decision records a reason', () => { + for (const d of DECISIONS) { + expect(d.reason.length, `${d.type}.fields.${d.key}.${d.prop} records no reason`).toBeGreaterThan(40); + } + }); + + it('refuses an `echo` verdict that carries no per-locale reason — proved on a synthetic row', () => { + // Every verdict in this round is `translate`, so the rule has nothing to + // evaluate over DECISIONS. Assert it there AND prove the predicate fires, + // or the requirement is a phantom check that deleting would leave green. + expect(undeclaredEchoes(DECISIONS), 'a declared echo here carries no reason').toEqual([]); + + const synthetic: Decision = { + type: 'object', + key: 'fields.placeholder', + prop: 'label', + en: 'Placeholder', + verdict: { 'zh-CN': 'echo', 'ja-JP': 'translate', 'es-ES': 'translate' }, + reason: 'A synthetic row that exists only to prove this file can refuse an undeclared echo.', + }; + expect(undeclaredEchoes([synthetic])).toEqual(['zh-CN object.fields.fields.placeholder.label']); + expect( + undeclaredEchoes([ + { + ...synthetic, + departures: { + 'zh-CN': 'A departure reason long enough to satisfy the rule, standing in for a real judgement.', + }, + }, + ]), + ).toEqual([]); + }); +}); + +describe('#19403 — 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); + } + } + }); + } +}); + +describe('#19403 — the panel population, DERIVED (the pin no census had)', () => { + it('the derivation reaches this panel, and only this panel', () => { + // Lit — floors, not exact counts, so a field added to the editor is caught + // by the echo rule below rather than by a number nobody can interpret. + // Measured on base 0862063ba: 41 entries, 78 string leaves. + expect(PANEL_LEAVES.length).toBeGreaterThan(70); + expect(new Set(PANEL_LEAVES.map((l) => l.key)).size).toBeGreaterThan(35); + for (const key of ['fields.placeholder', 'fields.summaryOperations.function', 'fields.options.label']) { + expect( + PANEL_LEAVES.some((l) => l.key === key), + `${key} is on the object field-editor panel`, + ).toBe(true); + } + // Dark — the derivation is not "every key in the catalog". The object + // form's own top-level keys and the sibling `field` panel are outside it. + for (const key of ['name', 'label', 'enable.feeds']) { + expect(PANEL_LEAVES.some((l) => l.key === key), `${key} is not a field-editor leaf`).toBe(false); + } + expect(PANEL_LEAVES.every((l) => l.prop === 'label' || l.prop === 'helpText')).toBe(true); + }); + + it('no leaf on this panel reads its `en` source unless the ledger decided it is an echo', () => { + const undecided: string[] = []; + for (const [locale, forms] of TRANSLATED_LOCALES) { + for (const { key, prop, en } of PANEL_LEAVES) { + if (forms.object?.fields?.[key]?.[prop] !== en) continue; + const decided = DECISIONS.find((d) => d.key === key && d.prop === prop); + if (decided?.verdict[locale] === 'echo') continue; + undecided.push(`${locale} object.fields.${key}.${prop} (${JSON.stringify(en)})`); + } + } + expect( + undecided, + 'these leaves read their en source and no row in this ledger says that is right — decide them, do not refresh anything', + ).toEqual([]); + }); + + it('the derived predicate can fire — fed the `en` catalog it flags every leaf on the panel', () => { + // Dark. Same walk, with `en` standing in for a translated catalog: every + // leaf must come back flagged, or the green above means only that the walk + // found nothing. + const flagged = PANEL_LEAVES.filter( + ({ key, prop, en }) => (enMetadataForms as Record).object?.fields?.[key]?.[prop] === en, + ); + expect(flagged.length).toBe(PANEL_LEAVES.length); + }); +}); diff --git a/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts index 34c532953ac..8f480a6bbe3 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts @@ -109,8 +109,8 @@ export const zhCNMetadataForms: NonNullable = helpText: "新记录的默认值(JSON 字面量)" }, "fields.placeholder": { - label: "Placeholder", - helpText: "Hint text shown inside the empty input; disappears once a value is entered" + label: "占位文本", + helpText: "显示在空输入框内的提示文本;输入内容后消失" }, "fields.maxLength": { label: "最大长度", @@ -121,12 +121,12 @@ export const zhCNMetadataForms: NonNullable = helpText: "最小字符数" }, "fields.valueDomain": { - label: "Value Domain", - helpText: "Standard the written value must belong to; a write carrying a non-member is refused" + label: "值域", + helpText: "写入值必须归属的标准;写入非成员值将被拒绝" }, "fields.rows": { - label: "Rows", - helpText: "Inline editor height (text rows)" + label: "行数", + helpText: "内联编辑器高度(文本行数)" }, "fields.min": { label: "最小值", @@ -165,44 +165,44 @@ export const zhCNMetadataForms: NonNullable = helpText: "目标对象名称。tree 字段可省略;若填写,必须是本对象自身(树是同一对象内部的层级 — 链接到其他对象请用 lookup)" }, "fields.lookupFilters": { - label: "Lookup Filters", - helpText: "Filter rules applied to the picker ({field, operator, value})" + label: "Lookup 筛选条件", + helpText: "应用于选择器的筛选规则({field, operator, value})" }, "fields.deleteBehavior": { - label: "Delete Behavior", - helpText: "What happens when the referenced record is deleted" + label: "删除行为", + helpText: "被引用记录删除时的处理方式" }, "fields.multiple": { label: "多选", helpText: "允许选择多条记录" }, "fields.expression": { - label: "Expression", - helpText: "CEL formula expression" + label: "表达式", + helpText: "CEL 公式表达式" }, "fields.returnType": { label: "返回类型", helpText: "公式结果类型" }, "fields.summaryOperations": { - label: "Summary Operations", - helpText: "Roll-up: which child object, which field, which aggregation" + label: "汇总操作", + helpText: "汇总:取哪个子对象、哪个字段、用哪种聚合" }, "fields.summaryOperations.object": { - label: "Object", - helpText: "Source child object name" + label: "对象", + helpText: "来源子对象名称" }, "fields.summaryOperations.field": { - label: "Field", - helpText: "Field on the child object to aggregate (ignored for count)" + label: "字段", + helpText: "子对象上参与聚合的字段(count 时忽略)" }, "fields.summaryOperations.function": { - label: "Function", - helpText: "Aggregation function" + label: "函数", + helpText: "聚合函数" }, "fields.autonumberFormat": { - label: "Autonumber Format", - helpText: "e.g. \"INV-{0000}\"; date tokens {YYYY}/{MM}/{DD} and {field_name} interpolation supported" + label: "自动编号格式", + helpText: "例如 \"INV-{0000}\";支持日期占位符 {YYYY}/{MM}/{DD} 以及 {field_name} 插值" }, "fields.language": { label: "语言", @@ -213,16 +213,16 @@ export const zhCNMetadataForms: NonNullable = helpText: "在记录的活动时间线上概述该字段的变更" }, "fields.visibleWhen": { - label: "Visible When", - helpText: "CEL predicate — field is shown only when TRUE" + label: "可见条件", + helpText: "CEL 判定式——结果为 TRUE 时才显示该字段" }, "fields.readonlyWhen": { - label: "Readonly When", - helpText: "CEL predicate — field is read-only when TRUE (enforced server-side)" + label: "只读条件", + helpText: "CEL 判定式——结果为 TRUE 时该字段只读(由服务端强制)" }, "fields.requiredWhen": { - label: "Required When", - helpText: "CEL predicate — field is required when TRUE (enforced server-side)" + label: "必填条件", + helpText: "CEL 判定式——结果为 TRUE 时该字段必填(由服务端强制)" }, enable: { label: "Enable", diff --git a/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts index 64e90a33bdd..8a64bdd6278 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts @@ -101,34 +101,6 @@ export const zhCNGeneratedSourceHashes: Readonly> = { "metadataForms.object.fields.enable.label": "fdecf828d4e86eed", "metadataForms.object.fields.enable.searchable.label": "d8f50099e7217830", "metadataForms.object.fields.enable.trackHistory.label": "7bc28212817b2c43", - "metadataForms.object.fields.fields.autonumberFormat.helpText": "14efe56f7cf8812e", - "metadataForms.object.fields.fields.autonumberFormat.label": "ced754e3dde1e78f", - "metadataForms.object.fields.fields.deleteBehavior.helpText": "6d94538a80df113e", - "metadataForms.object.fields.fields.deleteBehavior.label": "01c3c66572ce674b", - "metadataForms.object.fields.fields.expression.helpText": "6c559ad697dbaa85", - "metadataForms.object.fields.fields.expression.label": "cd1b52ad1588075e", - "metadataForms.object.fields.fields.lookupFilters.helpText": "ce8004df6ef768f1", - "metadataForms.object.fields.fields.lookupFilters.label": "c2860cdcfe74ad9a", - "metadataForms.object.fields.fields.placeholder.helpText": "497ea6299e075744", - "metadataForms.object.fields.fields.placeholder.label": "44d62b55b63fe718", - "metadataForms.object.fields.fields.readonlyWhen.helpText": "1daba886fd4e7f25", - "metadataForms.object.fields.fields.readonlyWhen.label": "af1c3f486fb2fbf7", - "metadataForms.object.fields.fields.requiredWhen.helpText": "997a0d21e5e32203", - "metadataForms.object.fields.fields.requiredWhen.label": "89b4d31ea46e2e76", - "metadataForms.object.fields.fields.rows.helpText": "3adb8c4c447f36b7", - "metadataForms.object.fields.fields.rows.label": "319af74cc41ea823", - "metadataForms.object.fields.fields.summaryOperations.field.helpText": "ff2f535b8f9cfffe", - "metadataForms.object.fields.fields.summaryOperations.field.label": "e21c314685cd95ca", - "metadataForms.object.fields.fields.summaryOperations.function.helpText": "b1022db50ca584b3", - "metadataForms.object.fields.fields.summaryOperations.function.label": "9ae3f998b780a051", - "metadataForms.object.fields.fields.summaryOperations.helpText": "4657cc4f93ecc549", - "metadataForms.object.fields.fields.summaryOperations.label": "bcf65520573738ee", - "metadataForms.object.fields.fields.summaryOperations.object.helpText": "7e6fc21663360844", - "metadataForms.object.fields.fields.summaryOperations.object.label": "d0c49032d95ebb36", - "metadataForms.object.fields.fields.valueDomain.helpText": "649e2e89dfeb2e8a", - "metadataForms.object.fields.fields.valueDomain.label": "18ebeb7e56e792a1", - "metadataForms.object.fields.fields.visibleWhen.helpText": "46de2d3ff57b6667", - "metadataForms.object.fields.fields.visibleWhen.label": "c852d4249db93285", "metadataForms.object.fields.validations.helpText": "1f6e59e364995ce8", "metadataForms.object.fields.validations.label": "06fc6dd288348779", "metadataForms.page.fields.interfaceConfig.addRecord.helpText": "3eb7b86c3a630db9",