From a7908873677169f19115231aed2fb7b9153eab63 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 05:49:33 +0000 Subject: [PATCH 1/6] fix(spec): view form spells out its composite children so their rows get catalog keys The view form declared selection, pagination, userFilters, appearance, userActions, navigation and sharing as composites with no children, so the i18n extractor emitted no key for any of their rows. Each child is now declared with the hint the panel already showed (its schema description), userFilters.fields is enumerated as a repeater with titled row properties, and the retired navigation.view / sharing.type tombstones are no longer offered. Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv Co-authored-by: Claude --- .../ui/view-form-composite-children.test.ts | 114 ++++++++++++++++ packages/spec/src/ui/view.form.ts | 123 ++++++++++++++++-- packages/spec/src/ui/view.zod.ts | 22 +++- 3 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 packages/spec/src/ui/view-form-composite-children.test.ts diff --git a/packages/spec/src/ui/view-form-composite-children.test.ts b/packages/spec/src/ui/view-form-composite-children.test.ts new file mode 100644 index 00000000000..9e1e19eb69a --- /dev/null +++ b/packages/spec/src/ui/view-form-composite-children.test.ts @@ -0,0 +1,114 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * #30267 — the view form spells out every composite's children, and each + * child's hint is the schema's own `.describe()` text. + * + * ## Why the children are declared at all + * + * `os i18n extract` walks a form field's DECLARED `fields` and nothing else. + * A composite left to the panel's schema-derived list (`SchemaForm` falls back + * to `schema.properties` when the form names no children) therefore had no + * `metadataForms.view.fields['.']` key for any of its rows: a + * zh-CN Studio read ~70 English rows under 表格选项 / 分页 / 终端用户控制 / + * 导航与共享. + * + * ## What declaring them costs, pinned here + * + * - **Completeness.** Once a composite declares children the panel renders + * exactly those, so a key added to the schema block later would silently + * drop out of the panel. Each declared list is held EQUAL to the schema's + * live keys — every property except the `retiredKey()` tombstones, which a + * form must never offer (the schema-derived list used to render + * `navigation.view` and `sharing.type` as editable rows). + * - **One English string.** A declared `helpText` replaces the schema + * description the panel showed before, so it is held equal to that + * description: an English console reads the same hint it read before, and + * the two cannot drift apart. A child whose schema has no description + * (`navigation.mode`) declares none. + * - **Repeater row names.** `userFilters.fields` is enumerated as a repeater; + * each row child's `label` is the item schema's `.meta({ title })` — the + * column head the panel reads (`repeater-item-titles.test.ts` requires the + * titles, this file requires the two equal). + */ + +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; + +import { viewForm } from './view.form'; +import { ViewSchema } from './view.zod'; + +type Node = Record; +type FormField = { field?: string; type?: string; label?: string; helpText?: string; fields?: FormField[] }; + +const RETIRED_PREFIX = '[REMOVED] '; + +/** The list-view JSON Schema, derived the way Studio's view inspector derives it. */ +const LIST: Node = (z.toJSONSchema(ViewSchema, { io: 'input', unrepresentable: 'any' }) as Node).properties.list; + +function formField(name: string): FormField { + for (const section of (viewForm.sections ?? []) as Array<{ fields?: FormField[] }>) { + for (const f of section.fields ?? []) if (f?.field === name) return f; + } + throw new Error(`viewForm declares no top-level field \`${name}\``); +} + +function liveKeys(props: Record): string[] { + return Object.entries(props) + .filter(([, v]) => !String(v?.description ?? '').startsWith(RETIRED_PREFIX)) + .map(([k]) => k); +} + +/** The composites this card spelled out, plus the one spelled out before it. */ +const COMPOSITES = ['selection', 'pagination', 'userFilters', 'appearance', 'userActions', 'addRecord', 'navigation', 'sharing'] as const; + +describe('#30267 — view form composites declare their children', () => { + it('reads a real schema (control: the derivation is not empty)', () => { + expect(Object.keys(LIST.properties.userActions.properties)).toContain('editInline'); + expect(LIST.properties.navigation.properties.view.description.startsWith(RETIRED_PREFIX)).toBe(true); + }); + + for (const name of COMPOSITES) { + it(`${name}: declares exactly the schema's live keys, in a composite`, () => { + const f = formField(name); + expect(f.type).toBe('composite'); + const declared = (f.fields ?? []).map((c) => c.field); + expect(declared.length, `${name} declares no children`).toBeGreaterThan(0); + expect([...declared].sort()).toEqual(liveKeys(LIST.properties[name].properties).sort()); + }); + + it(`${name}: each declared hint is the schema description it replaces`, () => { + const f = formField(name); + const props = LIST.properties[name].properties as Record; + for (const child of f.fields ?? []) { + expect(child.helpText, `${name}.${child.field}`).toBe(props[child.field!]?.description); + } + }); + } + + it('never offers a retired key: navigation.view, sharing.type, addRecord.mode / formView', () => { + const names = (n: string) => (formField(n).fields ?? []).map((c) => c.field); + expect(names('navigation')).not.toContain('view'); + expect(names('sharing')).not.toContain('type'); + expect(names('addRecord')).not.toContain('mode'); + expect(names('addRecord')).not.toContain('formView'); + }); + + it('top-level rows that gained a hint carry their schema description', () => { + for (const name of ['resizable', 'compactToolbar', 'rowHeight', 'selection', 'pagination', 'addRecord', 'showRecordCount', 'navigation', 'sharing']) { + expect(formField(name).helpText, name).toBe(LIST.properties[name].description); + } + }); + + it('userFilters.fields is a repeater whose row labels are the item schema titles', () => { + const repeater = (formField('userFilters').fields ?? []).find((c) => c.field === 'fields'); + expect(repeater?.type).toBe('repeater'); + const items = LIST.properties.userFilters.properties.fields.items.properties as Record; + const rows = repeater?.fields ?? []; + expect(rows.map((r) => r.field).sort()).toEqual(liveKeys(items).sort()); + for (const row of rows) { + expect(row.label, `userFilters.fields.${row.field}`).toBe(items[row.field!]?.title); + expect(typeof row.label).toBe('string'); + } + }); +}); diff --git a/packages/spec/src/ui/view.form.ts b/packages/spec/src/ui/view.form.ts index a067590c1cb..83a0a69eec8 100644 --- a/packages/spec/src/ui/view.form.ts +++ b/packages/spec/src/ui/view.form.ts @@ -11,6 +11,15 @@ * * Visibility predicates use the `type` discriminator to reveal only * the surface-specific block. + * + * Composite children are spelled out, each with the hint the panel shows + * (#30267). `os i18n extract` walks only DECLARED children, so a composite + * left to the schema-derived list had no catalog key for any of its rows and + * read English in every locale. A declared child's `helpText` is its schema + * `.describe()` text, the hint the panel showed before it was declared; + * `view-form-composite-children.test.ts` holds the two equal and holds each + * child list equal to the schema's live keys, so a key added to a block + * cannot go missing from the panel. */ import { defineForm } from './view.zod'; @@ -95,11 +104,17 @@ export const viewForm = defineForm({ collapsed: true, columns: 2, fields: [ - { field: 'resizable', colSpan: 1 }, + { field: 'resizable', colSpan: 1, helpText: 'Enable column resizing' }, // `striped` / `bordered` inputs removed with the keys (#7176 — pass-through-only, retired). - { field: 'compactToolbar', colSpan: 1 }, - { field: 'rowHeight', colSpan: 1 }, - { field: 'selection', type: 'composite', colSpan: 2 }, + { field: 'compactToolbar', colSpan: 1, helpText: 'Collapse Group/Color/Density/Hide-fields into a single View settings popover' }, + { field: 'rowHeight', colSpan: 1, helpText: 'Row height / density setting' }, + { + field: 'selection', + type: 'composite', + colSpan: 2, + helpText: 'Row selection configuration', + fields: [{ field: 'type', helpText: 'Selection mode' }], + }, ], }, // `pagination` is NOT grid-only: every view type accepts it, and @@ -113,7 +128,17 @@ export const viewForm = defineForm({ description: 'Page size and page-size options — every view type accepts them, not only grids.', collapsible: true, collapsed: true, - fields: [{ field: 'pagination', type: 'composite' }], + fields: [ + { + field: 'pagination', + type: 'composite', + helpText: 'Pagination configuration', + fields: [ + { field: 'pageSize', helpText: 'Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set' }, + { field: 'pageSizeOptions', helpText: 'Available page size options' }, + ], + }, + ], }, { name: 'kanban', @@ -172,14 +197,62 @@ export const viewForm = defineForm({ collapsible: true, collapsed: true, fields: [ - { field: 'userFilters', type: 'composite', helpText: 'Quick-filter bar: element style (dropdown / tabs / toggle) + exposed fields or tab presets' }, + { + field: 'userFilters', + type: 'composite', + helpText: 'Quick-filter bar: element style (dropdown / tabs / toggle) + exposed fields or tab presets', + fields: [ + { field: 'element', helpText: 'Filter control style on object views: "dropdown" (per-field value chips). "toggle" is deprecated. "tabs" is page-only — use `listViews` for named presets.' }, + { + field: 'fields', + type: 'repeater', + helpText: 'Fields exposed as quick filters (dropdown/toggle elements)', + // Row-property names: every authorable row property, `label` + // equal to the item schema's `.meta({ title })`, as `columns` + // above does. + fields: [ + { field: 'field', label: 'Field' }, + { field: 'label', label: 'Label' }, + { field: 'type', label: 'Type' }, + { field: 'options', label: 'Options' }, + { field: 'showCount', label: 'Show Count' }, + { field: 'defaultValues', label: 'Default Values' }, + ], + }, + ], + }, // [#20301] The `tabs` repeater was REMOVED with the key it wrote — now a // `retiredKey()` tombstone on `ListViewSchema` (no renderer ever mounted a // tab bar for it). A form input for an unwritable key is the // false-compliant UI half of a retirement; named presets are `listViews` // entries, each a tab in the saved-view switcher. - { field: 'appearance', type: 'composite', helpText: 'allowedVisualizations: which renderers users may switch between' }, - { field: 'userActions', type: 'composite', helpText: 'Toolbar toggles: sort / search / filter / row height' }, + { + field: 'appearance', + type: 'composite', + helpText: 'allowedVisualizations: which renderers users may switch between', + fields: [ + { field: 'showDescription', helpText: 'Show the view description text' }, + { field: 'allowedVisualizations', helpText: 'Whitelist of visualization types users can switch between (e.g. ["grid", "gallery", "kanban"])' }, + ], + }, + { + field: 'userActions', + type: 'composite', + helpText: 'Toolbar toggles: sort / search / filter / row height', + fields: [ + { field: 'sort', helpText: 'Allow users to sort records' }, + { field: 'search', helpText: 'Allow users to search records' }, + { field: 'filter', helpText: 'Allow users to filter records' }, + { field: 'refresh', helpText: 'Allow users to reload the view data from the backend without a full page reload' }, + { field: 'rowHeight', helpText: 'Allow users to toggle row height/density' }, + { field: 'group', helpText: 'Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block.' }, + { field: 'addRecordForm', helpText: 'Add records through a form instead of inline' }, + { field: 'editInline', helpText: 'Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field\'s type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place.' }, + { field: 'hideFields', helpText: 'Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component\'s `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control.' }, + { field: 'rowColor', helpText: 'Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in.' }, + { field: 'buttons', helpText: 'Custom action button IDs to show in the toolbar' }, + ], + }, // [#24692] Spelled out so the form offers exactly the two live keys: // `mode` and `formView` are `retiredKey()` tombstones on // `AddRecordConfigSchema` (no renderer ever read them — the Add button @@ -189,9 +262,13 @@ export const viewForm = defineForm({ { field: 'addRecord', type: 'composite', - fields: [{ field: 'enabled' }, { field: 'position' }], + helpText: 'Add record entry point configuration', + fields: [ + { field: 'enabled', helpText: 'Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off' }, + { field: 'position', helpText: 'Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both`' }, + ], }, - { field: 'showRecordCount' }, + { field: 'showRecordCount', helpText: 'Show record count at the bottom of the list' }, ], }, { @@ -201,8 +278,30 @@ export const viewForm = defineForm({ collapsible: true, collapsed: true, fields: [ - { field: 'navigation', type: 'composite' }, - { field: 'sharing', type: 'composite' }, + // Children spelled out (#30267) so the i18n extractor emits a catalog + // key per row, and so the form offers only the live keys: + // `navigation.view` and `sharing.type` are `retiredKey()` tombstones + // the schema-derived list used to render as editable rows. + { + field: 'navigation', + type: 'composite', + helpText: 'Configuration for item click navigation (drawer by default, page, new_window or none)', + fields: [ + { field: 'mode' }, + { field: 'preventNavigation', helpText: 'Disable standard navigation entirely' }, + { field: 'openNewTab', helpText: 'Force open in new tab (applies to page mode)' }, + { field: 'size', helpText: 'Overlay size bucket for the drawer detail: \'auto\' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it.' }, + { field: 'width', helpText: '[DEPRECATED → size] Pixel/percent width of the drawer (e.g. "600px"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket.' }, + ], + }, + { + field: 'sharing', + type: 'composite', + helpText: 'View sharing configuration: who locked the view configuration', + fields: [ + { field: 'lockedBy', helpText: 'User who locked the view configuration' }, + ], + }, ], }, ], diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 3dba7823944..c5aff8384db 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -2007,16 +2007,24 @@ export const ViewTabSchema = lazySchema(() => strictObject({ * Rendering details (widget, options) default to inference from the field * definition on the source object — authors only override when needed. * + * Each property carries a `.meta({ title })` because `view.form.ts` enumerates + * `userFilters.fields` as a repeater: the title is the English column head + * Studio reads, and the form's declared row `label` repeats it so the i18n + * extractor emits a catalog key per column (#30267). + * * @see Airtable Interface → "User filters" panel (Dropdowns element) */ export const UserFilterFieldSchema = lazySchema(() => strictObject({ surface: 'this user filter field', history: VIEW_HISTORY, }, { - field: z.string().describe('Field name on the source object (must exist — checked by reference diagnostics)'), - label: I18nLabelSchema.optional().describe('Display label override (defaults to the field label)'), + field: z.string().describe('Field name on the source object (must exist — checked by reference diagnostics)') + .meta({ title: 'Field' }), + label: I18nLabelSchema.optional().describe('Display label override (defaults to the field label)') + .meta({ title: 'Label' }), type: z.enum(['select', 'multi-select', 'boolean', 'date-range', 'text']).optional() - .describe('Filter control type. Omit to infer from the field definition'), + .describe('Filter control type. Omit to infer from the field definition') + .meta({ title: 'Type' }), options: z.array(strictObject({ surface: 'this user filter option', history: VIEW_HISTORY, @@ -2033,10 +2041,12 @@ export const UserFilterFieldSchema = lazySchema(() => strictObject({ value: z.union([z.string(), z.number(), z.boolean()]).describe('Option value'), label: I18nLabelSchema.describe('Option label'), color: z.string().optional().describe('Option color token/hex'), - })).optional().describe('Static options. Omit to derive from the field definition (select options / lookup records)'), - showCount: z.boolean().optional().describe('Show per-option record counts'), + })).optional().describe('Static options. Omit to derive from the field definition (select options / lookup records)') + .meta({ title: 'Options' }), + showCount: z.boolean().optional().describe('Show per-option record counts').meta({ title: 'Show Count' }), defaultValues: z.array(z.union([z.string(), z.number(), z.boolean()])).optional() - .describe('Pre-selected values when the view loads'), + .describe('Pre-selected values when the view loads') + .meta({ title: 'Default Values' }), }).describe('Quick-filter field configuration')); /** From abfec1427673bc79df86330579973b2fab8960b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 05:54:31 +0000 Subject: [PATCH 2/6] fix(platform-objects): zh-CN rows for the view form's composite children Regenerated the metadata-forms catalogs for the newly declared view children and translated every new zh-CN leaf. ja-JP and es-ES carry their own text for the six userFilters.fields row properties (the repeater-row guard refuses an en echo there); their other new leaves are en fills, as the extractor leaves them. Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv Co-authored-by: Claude --- .../en.metadata-forms.generated.ts | 146 ++++++++++++++++-- .../es-ES.metadata-forms.generated.ts | 146 ++++++++++++++++-- .../es-ES.source-hashes.generated.ts | 58 +++++++ .../ja-JP.metadata-forms.generated.ts | 146 ++++++++++++++++-- .../ja-JP.source-hashes.generated.ts | 58 +++++++ .../zh-CN.metadata-forms.generated.ts | 146 ++++++++++++++++-- 6 files changed, 656 insertions(+), 44 deletions(-) diff --git a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts index d7993bd5eb7..79ddbbc3c91 100644 --- a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts @@ -1154,19 +1154,36 @@ export const enMetadataForms: NonNullable = { helpText: "Field names available for quick search" }, resizable: { - label: "Resizable" + label: "Resizable", + helpText: "Enable column resizing" }, compactToolbar: { - label: "Compact Toolbar" + label: "Compact Toolbar", + helpText: "Collapse Group/Color/Density/Hide-fields into a single View settings popover" }, rowHeight: { - label: "Row Height" + label: "Row Height", + helpText: "Row height / density setting" }, selection: { - label: "Selection" + label: "Selection", + helpText: "Row selection configuration" + }, + "selection.type": { + label: "Type", + helpText: "Selection mode" }, pagination: { - label: "Pagination" + label: "Pagination", + helpText: "Pagination configuration" + }, + "pagination.pageSize": { + label: "Page Size", + helpText: "Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set" + }, + "pagination.pageSizeOptions": { + label: "Page Size Options", + helpText: "Available page size options" }, kanban: { label: "Kanban" @@ -1190,31 +1207,138 @@ export const enMetadataForms: NonNullable = { label: "User Filters", helpText: "Quick-filter bar: element style (dropdown / tabs / toggle) + exposed fields or tab presets" }, + "userFilters.element": { + label: "Element", + helpText: "Filter control style on object views: \"dropdown\" (per-field value chips). \"toggle\" is deprecated. \"tabs\" is page-only — use `listViews` for named presets." + }, + "userFilters.fields": { + label: "Fields", + helpText: "Fields exposed as quick filters (dropdown/toggle elements)" + }, + "userFilters.fields.field": { + label: "Field" + }, + "userFilters.fields.label": { + label: "Label" + }, + "userFilters.fields.type": { + label: "Type" + }, + "userFilters.fields.options": { + label: "Options" + }, + "userFilters.fields.showCount": { + label: "Show Count" + }, + "userFilters.fields.defaultValues": { + label: "Default Values" + }, appearance: { label: "Appearance", helpText: "allowedVisualizations: which renderers users may switch between" }, + "appearance.showDescription": { + label: "Show Description", + helpText: "Show the view description text" + }, + "appearance.allowedVisualizations": { + label: "Allowed Visualizations", + helpText: "Whitelist of visualization types users can switch between (e.g. [\"grid\", \"gallery\", \"kanban\"])" + }, userActions: { label: "User Actions", helpText: "Toolbar toggles: sort / search / filter / row height" }, + "userActions.sort": { + label: "Sort", + helpText: "Allow users to sort records" + }, + "userActions.search": { + label: "Search", + helpText: "Allow users to search records" + }, + "userActions.filter": { + label: "Filter", + helpText: "Allow users to filter records" + }, + "userActions.refresh": { + label: "Refresh", + helpText: "Allow users to reload the view data from the backend without a full page reload" + }, + "userActions.rowHeight": { + label: "Row Height", + helpText: "Allow users to toggle row height/density" + }, + "userActions.group": { + label: "Group", + helpText: "Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block." + }, + "userActions.addRecordForm": { + label: "Add Record Form", + helpText: "Add records through a form instead of inline" + }, + "userActions.editInline": { + label: "Edit Inline", + helpText: "Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place." + }, + "userActions.hideFields": { + label: "Hide Fields", + helpText: "Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control." + }, + "userActions.rowColor": { + label: "Row Color", + helpText: "Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in." + }, + "userActions.buttons": { + label: "Buttons", + helpText: "Custom action button IDs to show in the toolbar" + }, addRecord: { - label: "Add Record" + label: "Add Record", + helpText: "Add record entry point configuration" }, "addRecord.enabled": { - label: "Enabled" + label: "Enabled", + helpText: "Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off" }, "addRecord.position": { - label: "Position" + label: "Position", + helpText: "Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both`" }, showRecordCount: { - label: "Show Record Count" + label: "Show Record Count", + helpText: "Show record count at the bottom of the list" }, navigation: { - label: "Navigation" + label: "Navigation", + helpText: "Configuration for item click navigation (drawer by default, page, new_window or none)" + }, + "navigation.mode": { + label: "Mode" + }, + "navigation.preventNavigation": { + label: "Prevent Navigation", + helpText: "Disable standard navigation entirely" + }, + "navigation.openNewTab": { + label: "Open New Tab", + helpText: "Force open in new tab (applies to page mode)" + }, + "navigation.size": { + label: "Size", + helpText: "Overlay size bucket for the drawer detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it." + }, + "navigation.width": { + label: "Width", + helpText: "[DEPRECATED → size] Pixel/percent width of the drawer (e.g. \"600px\"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket." }, sharing: { - label: "Sharing" + label: "Sharing", + helpText: "View sharing configuration: who locked the view configuration" + }, + "sharing.lockedBy": { + label: "Locked By", + helpText: "User who locked the view configuration" } } }, 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 b2a44f09fe4..61747fff76a 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 @@ -1154,19 +1154,36 @@ export const esESMetadataForms: NonNullable = helpText: "Nombres de campo disponibles para búsqueda rápida" }, resizable: { - label: "Redimensionable" + label: "Redimensionable", + helpText: "Enable column resizing" }, compactToolbar: { - label: "Barra compacta" + label: "Barra compacta", + helpText: "Collapse Group/Color/Density/Hide-fields into a single View settings popover" }, rowHeight: { - label: "Altura de fila" + label: "Altura de fila", + helpText: "Row height / density setting" }, selection: { - label: "Selección" + label: "Selección", + helpText: "Row selection configuration" + }, + "selection.type": { + label: "Type", + helpText: "Selection mode" }, pagination: { - label: "Paginación" + label: "Paginación", + helpText: "Pagination configuration" + }, + "pagination.pageSize": { + label: "Page Size", + helpText: "Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set" + }, + "pagination.pageSizeOptions": { + label: "Page Size Options", + helpText: "Available page size options" }, kanban: { label: "Configuración Kanban" @@ -1190,31 +1207,138 @@ export const esESMetadataForms: NonNullable = label: "Filtros de usuario", helpText: "Barra de filtros rápidos: estilo de elemento (dropdown / tabs / toggle) + campos expuestos o preajustes de pestañas" }, + "userFilters.element": { + label: "Element", + helpText: "Filter control style on object views: \"dropdown\" (per-field value chips). \"toggle\" is deprecated. \"tabs\" is page-only — use `listViews` for named presets." + }, + "userFilters.fields": { + label: "Fields", + helpText: "Fields exposed as quick filters (dropdown/toggle elements)" + }, + "userFilters.fields.field": { + label: "Campo" + }, + "userFilters.fields.label": { + label: "Etiqueta" + }, + "userFilters.fields.type": { + label: "Tipo" + }, + "userFilters.fields.options": { + label: "Opciones" + }, + "userFilters.fields.showCount": { + label: "Mostrar recuento" + }, + "userFilters.fields.defaultValues": { + label: "Valores predeterminados" + }, appearance: { label: "Apariencia", helpText: "allowedVisualizations: qué renderizadores pueden alternar los usuarios" }, + "appearance.showDescription": { + label: "Show Description", + helpText: "Show the view description text" + }, + "appearance.allowedVisualizations": { + label: "Allowed Visualizations", + helpText: "Whitelist of visualization types users can switch between (e.g. [\"grid\", \"gallery\", \"kanban\"])" + }, userActions: { label: "Acciones de usuario", helpText: "Conmutadores de la barra de herramientas: ordenar / buscar / filtrar / altura de fila" }, + "userActions.sort": { + label: "Sort", + helpText: "Allow users to sort records" + }, + "userActions.search": { + label: "Search", + helpText: "Allow users to search records" + }, + "userActions.filter": { + label: "Filter", + helpText: "Allow users to filter records" + }, + "userActions.refresh": { + label: "Refresh", + helpText: "Allow users to reload the view data from the backend without a full page reload" + }, + "userActions.rowHeight": { + label: "Row Height", + helpText: "Allow users to toggle row height/density" + }, + "userActions.group": { + label: "Group", + helpText: "Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block." + }, + "userActions.addRecordForm": { + label: "Add Record Form", + helpText: "Add records through a form instead of inline" + }, + "userActions.editInline": { + label: "Edit Inline", + helpText: "Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place." + }, + "userActions.hideFields": { + label: "Hide Fields", + helpText: "Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control." + }, + "userActions.rowColor": { + label: "Row Color", + helpText: "Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in." + }, + "userActions.buttons": { + label: "Buttons", + helpText: "Custom action button IDs to show in the toolbar" + }, addRecord: { - label: "Agregar registro" + label: "Agregar registro", + helpText: "Add record entry point configuration" }, "addRecord.enabled": { - label: "Habilitado" + label: "Habilitado", + helpText: "Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off" }, "addRecord.position": { - label: "Posición" + label: "Posición", + helpText: "Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both`" }, showRecordCount: { - label: "Mostrar recuento de registros" + label: "Mostrar recuento de registros", + helpText: "Show record count at the bottom of the list" }, navigation: { - label: "Navegación" + label: "Navegación", + helpText: "Configuration for item click navigation (drawer by default, page, new_window or none)" + }, + "navigation.mode": { + label: "Mode" + }, + "navigation.preventNavigation": { + label: "Prevent Navigation", + helpText: "Disable standard navigation entirely" + }, + "navigation.openNewTab": { + label: "Open New Tab", + helpText: "Force open in new tab (applies to page mode)" + }, + "navigation.size": { + label: "Size", + helpText: "Overlay size bucket for the drawer detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it." + }, + "navigation.width": { + label: "Width", + helpText: "[DEPRECATED → size] Pixel/percent width of the drawer (e.g. \"600px\"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket." }, sharing: { - label: "Compartición" + label: "Compartición", + helpText: "View sharing configuration: who locked the view configuration" + }, + "sharing.lockedBy": { + label: "Locked By", + helpText: "User who locked the view configuration" } } }, 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 4086c3c4669..43fd5149c9e 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 @@ -18,6 +18,64 @@ */ export const esESGeneratedSourceHashes: Readonly> = { + "metadataForms.view.fields.addRecord.enabled.helpText": "d5ddec5558f800c5", + "metadataForms.view.fields.addRecord.helpText": "f799b27dd56271e7", + "metadataForms.view.fields.addRecord.position.helpText": "5ab594c22382a907", + "metadataForms.view.fields.appearance.allowedVisualizations.helpText": "6bc4ed77538231a6", + "metadataForms.view.fields.appearance.allowedVisualizations.label": "cd66b3b2fba2ab6f", + "metadataForms.view.fields.appearance.showDescription.helpText": "03a16d9590306528", + "metadataForms.view.fields.appearance.showDescription.label": "ae53a515325846d0", + "metadataForms.view.fields.compactToolbar.helpText": "c5e34d8f95fb5382", + "metadataForms.view.fields.navigation.helpText": "8b7f58dcdd7cfc25", + "metadataForms.view.fields.navigation.mode.label": "74dde5013435abdc", + "metadataForms.view.fields.navigation.openNewTab.helpText": "5779e8754f2625d7", + "metadataForms.view.fields.navigation.openNewTab.label": "5c49a752788679bc", + "metadataForms.view.fields.navigation.preventNavigation.helpText": "4cf351bc5e780666", + "metadataForms.view.fields.navigation.preventNavigation.label": "6a51a7fe68a1ef8b", + "metadataForms.view.fields.navigation.size.helpText": "0c54496e69c5dc0d", + "metadataForms.view.fields.navigation.size.label": "41f9ade673bbbc10", + "metadataForms.view.fields.navigation.width.helpText": "18acfadac0e77c3b", + "metadataForms.view.fields.navigation.width.label": "99a50f2407f41c32", + "metadataForms.view.fields.pagination.helpText": "823ad0f6e9ccc45f", + "metadataForms.view.fields.pagination.pageSize.helpText": "6d5dd7640a1e7096", + "metadataForms.view.fields.pagination.pageSize.label": "21cf295f387dffb5", + "metadataForms.view.fields.pagination.pageSizeOptions.helpText": "30b3c8b5475ac828", + "metadataForms.view.fields.pagination.pageSizeOptions.label": "979e0594bd63ebb7", + "metadataForms.view.fields.resizable.helpText": "697608babf720579", + "metadataForms.view.fields.rowHeight.helpText": "b3fd4fcdd8a002fb", + "metadataForms.view.fields.selection.helpText": "9ffc2ff5ab629fd1", + "metadataForms.view.fields.selection.type.helpText": "e5dd21f1530d0d2f", + "metadataForms.view.fields.selection.type.label": "901bb552898b2cab", + "metadataForms.view.fields.sharing.helpText": "29a59b2fb250f3d5", + "metadataForms.view.fields.sharing.lockedBy.helpText": "351bd9e70614d07f", + "metadataForms.view.fields.sharing.lockedBy.label": "93a358a9cfb08f52", + "metadataForms.view.fields.showRecordCount.helpText": "eb9ae575ad0a0efd", + "metadataForms.view.fields.userActions.addRecordForm.helpText": "a018a68bfadbbec9", + "metadataForms.view.fields.userActions.addRecordForm.label": "9f75b8792c5a9ca6", + "metadataForms.view.fields.userActions.buttons.helpText": "1f2a3891a103f76d", + "metadataForms.view.fields.userActions.buttons.label": "213eb6a492478539", + "metadataForms.view.fields.userActions.editInline.helpText": "18352e04aba3b9cf", + "metadataForms.view.fields.userActions.editInline.label": "d0d5c3713f3280db", + "metadataForms.view.fields.userActions.filter.helpText": "54accbfbacddbafb", + "metadataForms.view.fields.userActions.filter.label": "802895ec427fc6af", + "metadataForms.view.fields.userActions.group.helpText": "5ab0a0e6af342edb", + "metadataForms.view.fields.userActions.group.label": "588222bcabb4c16e", + "metadataForms.view.fields.userActions.hideFields.helpText": "a04c7e48a82df497", + "metadataForms.view.fields.userActions.hideFields.label": "76f0012fdee673b5", + "metadataForms.view.fields.userActions.refresh.helpText": "611966483c1d8aa5", + "metadataForms.view.fields.userActions.refresh.label": "1b63ae2e002930a6", + "metadataForms.view.fields.userActions.rowColor.helpText": "d34658e12c5b91b2", + "metadataForms.view.fields.userActions.rowColor.label": "6d6bd56442a27c9c", + "metadataForms.view.fields.userActions.rowHeight.helpText": "622077b9bf4e2edd", + "metadataForms.view.fields.userActions.rowHeight.label": "4ac338620533198d", + "metadataForms.view.fields.userActions.search.helpText": "32a9fcaaaf8e8fb4", + "metadataForms.view.fields.userActions.search.label": "e09aaf5d7e06f528", + "metadataForms.view.fields.userActions.sort.helpText": "624d1ed3daeddaf6", + "metadataForms.view.fields.userActions.sort.label": "4c590a0ed0c02eff", + "metadataForms.view.fields.userFilters.element.helpText": "a41896f00679ee07", + "metadataForms.view.fields.userFilters.element.label": "e480e4fe57239c98", + "metadataForms.view.fields.userFilters.fields.helpText": "d312e9549dc8c5cd", + "metadataForms.view.fields.userFilters.fields.label": "f316ec6025047133", "objects.sys_email.fields.cc_addresses.label": "02946d952cf15623", "objects.sys_email.fields.error.label": "786fed84bd8d5a32", "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", 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 dbe5b24c425..d2856094e7e 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 @@ -1154,19 +1154,36 @@ export const jaJPMetadataForms: NonNullable = helpText: "クイック検索で使用可能なフィールド名" }, resizable: { - label: "サイズ変更可" + label: "サイズ変更可", + helpText: "Enable column resizing" }, compactToolbar: { - label: "コンパクトツールバー" + label: "コンパクトツールバー", + helpText: "Collapse Group/Color/Density/Hide-fields into a single View settings popover" }, rowHeight: { - label: "行の高さ" + label: "行の高さ", + helpText: "Row height / density setting" }, selection: { - label: "選択" + label: "選択", + helpText: "Row selection configuration" + }, + "selection.type": { + label: "Type", + helpText: "Selection mode" }, pagination: { - label: "ページネーション" + label: "ページネーション", + helpText: "Pagination configuration" + }, + "pagination.pageSize": { + label: "Page Size", + helpText: "Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set" + }, + "pagination.pageSizeOptions": { + label: "Page Size Options", + helpText: "Available page size options" }, kanban: { label: "カンバン" @@ -1190,31 +1207,138 @@ export const jaJPMetadataForms: NonNullable = label: "ユーザーフィルター", helpText: "クイックフィルターバー:要素スタイル(dropdown / tabs / toggle)+ 公開フィールドまたはタブプリセット" }, + "userFilters.element": { + label: "Element", + helpText: "Filter control style on object views: \"dropdown\" (per-field value chips). \"toggle\" is deprecated. \"tabs\" is page-only — use `listViews` for named presets." + }, + "userFilters.fields": { + label: "Fields", + helpText: "Fields exposed as quick filters (dropdown/toggle elements)" + }, + "userFilters.fields.field": { + label: "フィールド" + }, + "userFilters.fields.label": { + label: "表示名" + }, + "userFilters.fields.type": { + label: "型" + }, + "userFilters.fields.options": { + label: "選択肢" + }, + "userFilters.fields.showCount": { + label: "件数を表示" + }, + "userFilters.fields.defaultValues": { + label: "既定値" + }, appearance: { label: "外観", helpText: "allowedVisualizations:ユーザーが切り替え可能なレンダラー" }, + "appearance.showDescription": { + label: "Show Description", + helpText: "Show the view description text" + }, + "appearance.allowedVisualizations": { + label: "Allowed Visualizations", + helpText: "Whitelist of visualization types users can switch between (e.g. [\"grid\", \"gallery\", \"kanban\"])" + }, userActions: { label: "ユーザーアクション", helpText: "ツールバーの切り替え:並べ替え / 検索 / フィルター / 行の高さ" }, + "userActions.sort": { + label: "Sort", + helpText: "Allow users to sort records" + }, + "userActions.search": { + label: "Search", + helpText: "Allow users to search records" + }, + "userActions.filter": { + label: "Filter", + helpText: "Allow users to filter records" + }, + "userActions.refresh": { + label: "Refresh", + helpText: "Allow users to reload the view data from the backend without a full page reload" + }, + "userActions.rowHeight": { + label: "Row Height", + helpText: "Allow users to toggle row height/density" + }, + "userActions.group": { + label: "Group", + helpText: "Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block." + }, + "userActions.addRecordForm": { + label: "Add Record Form", + helpText: "Add records through a form instead of inline" + }, + "userActions.editInline": { + label: "Edit Inline", + helpText: "Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place." + }, + "userActions.hideFields": { + label: "Hide Fields", + helpText: "Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control." + }, + "userActions.rowColor": { + label: "Row Color", + helpText: "Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in." + }, + "userActions.buttons": { + label: "Buttons", + helpText: "Custom action button IDs to show in the toolbar" + }, addRecord: { - label: "レコードを追加" + label: "レコードを追加", + helpText: "Add record entry point configuration" }, "addRecord.enabled": { - label: "有効" + label: "有効", + helpText: "Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off" }, "addRecord.position": { - label: "位置" + label: "位置", + helpText: "Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both`" }, showRecordCount: { - label: "レコード数を表示" + label: "レコード数を表示", + helpText: "Show record count at the bottom of the list" }, navigation: { - label: "ナビゲーション" + label: "ナビゲーション", + helpText: "Configuration for item click navigation (drawer by default, page, new_window or none)" + }, + "navigation.mode": { + label: "Mode" + }, + "navigation.preventNavigation": { + label: "Prevent Navigation", + helpText: "Disable standard navigation entirely" + }, + "navigation.openNewTab": { + label: "Open New Tab", + helpText: "Force open in new tab (applies to page mode)" + }, + "navigation.size": { + label: "Size", + helpText: "Overlay size bucket for the drawer detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it." + }, + "navigation.width": { + label: "Width", + helpText: "[DEPRECATED → size] Pixel/percent width of the drawer (e.g. \"600px\"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket." }, sharing: { - label: "共有" + label: "共有", + helpText: "View sharing configuration: who locked the view configuration" + }, + "sharing.lockedBy": { + label: "Locked By", + helpText: "User who locked the view configuration" } } }, 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 1582af3d0ee..5018ab573b3 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 @@ -18,6 +18,64 @@ */ export const jaJPGeneratedSourceHashes: Readonly> = { + "metadataForms.view.fields.addRecord.enabled.helpText": "d5ddec5558f800c5", + "metadataForms.view.fields.addRecord.helpText": "f799b27dd56271e7", + "metadataForms.view.fields.addRecord.position.helpText": "5ab594c22382a907", + "metadataForms.view.fields.appearance.allowedVisualizations.helpText": "6bc4ed77538231a6", + "metadataForms.view.fields.appearance.allowedVisualizations.label": "cd66b3b2fba2ab6f", + "metadataForms.view.fields.appearance.showDescription.helpText": "03a16d9590306528", + "metadataForms.view.fields.appearance.showDescription.label": "ae53a515325846d0", + "metadataForms.view.fields.compactToolbar.helpText": "c5e34d8f95fb5382", + "metadataForms.view.fields.navigation.helpText": "8b7f58dcdd7cfc25", + "metadataForms.view.fields.navigation.mode.label": "74dde5013435abdc", + "metadataForms.view.fields.navigation.openNewTab.helpText": "5779e8754f2625d7", + "metadataForms.view.fields.navigation.openNewTab.label": "5c49a752788679bc", + "metadataForms.view.fields.navigation.preventNavigation.helpText": "4cf351bc5e780666", + "metadataForms.view.fields.navigation.preventNavigation.label": "6a51a7fe68a1ef8b", + "metadataForms.view.fields.navigation.size.helpText": "0c54496e69c5dc0d", + "metadataForms.view.fields.navigation.size.label": "41f9ade673bbbc10", + "metadataForms.view.fields.navigation.width.helpText": "18acfadac0e77c3b", + "metadataForms.view.fields.navigation.width.label": "99a50f2407f41c32", + "metadataForms.view.fields.pagination.helpText": "823ad0f6e9ccc45f", + "metadataForms.view.fields.pagination.pageSize.helpText": "6d5dd7640a1e7096", + "metadataForms.view.fields.pagination.pageSize.label": "21cf295f387dffb5", + "metadataForms.view.fields.pagination.pageSizeOptions.helpText": "30b3c8b5475ac828", + "metadataForms.view.fields.pagination.pageSizeOptions.label": "979e0594bd63ebb7", + "metadataForms.view.fields.resizable.helpText": "697608babf720579", + "metadataForms.view.fields.rowHeight.helpText": "b3fd4fcdd8a002fb", + "metadataForms.view.fields.selection.helpText": "9ffc2ff5ab629fd1", + "metadataForms.view.fields.selection.type.helpText": "e5dd21f1530d0d2f", + "metadataForms.view.fields.selection.type.label": "901bb552898b2cab", + "metadataForms.view.fields.sharing.helpText": "29a59b2fb250f3d5", + "metadataForms.view.fields.sharing.lockedBy.helpText": "351bd9e70614d07f", + "metadataForms.view.fields.sharing.lockedBy.label": "93a358a9cfb08f52", + "metadataForms.view.fields.showRecordCount.helpText": "eb9ae575ad0a0efd", + "metadataForms.view.fields.userActions.addRecordForm.helpText": "a018a68bfadbbec9", + "metadataForms.view.fields.userActions.addRecordForm.label": "9f75b8792c5a9ca6", + "metadataForms.view.fields.userActions.buttons.helpText": "1f2a3891a103f76d", + "metadataForms.view.fields.userActions.buttons.label": "213eb6a492478539", + "metadataForms.view.fields.userActions.editInline.helpText": "18352e04aba3b9cf", + "metadataForms.view.fields.userActions.editInline.label": "d0d5c3713f3280db", + "metadataForms.view.fields.userActions.filter.helpText": "54accbfbacddbafb", + "metadataForms.view.fields.userActions.filter.label": "802895ec427fc6af", + "metadataForms.view.fields.userActions.group.helpText": "5ab0a0e6af342edb", + "metadataForms.view.fields.userActions.group.label": "588222bcabb4c16e", + "metadataForms.view.fields.userActions.hideFields.helpText": "a04c7e48a82df497", + "metadataForms.view.fields.userActions.hideFields.label": "76f0012fdee673b5", + "metadataForms.view.fields.userActions.refresh.helpText": "611966483c1d8aa5", + "metadataForms.view.fields.userActions.refresh.label": "1b63ae2e002930a6", + "metadataForms.view.fields.userActions.rowColor.helpText": "d34658e12c5b91b2", + "metadataForms.view.fields.userActions.rowColor.label": "6d6bd56442a27c9c", + "metadataForms.view.fields.userActions.rowHeight.helpText": "622077b9bf4e2edd", + "metadataForms.view.fields.userActions.rowHeight.label": "4ac338620533198d", + "metadataForms.view.fields.userActions.search.helpText": "32a9fcaaaf8e8fb4", + "metadataForms.view.fields.userActions.search.label": "e09aaf5d7e06f528", + "metadataForms.view.fields.userActions.sort.helpText": "624d1ed3daeddaf6", + "metadataForms.view.fields.userActions.sort.label": "4c590a0ed0c02eff", + "metadataForms.view.fields.userFilters.element.helpText": "a41896f00679ee07", + "metadataForms.view.fields.userFilters.element.label": "e480e4fe57239c98", + "metadataForms.view.fields.userFilters.fields.helpText": "d312e9549dc8c5cd", + "metadataForms.view.fields.userFilters.fields.label": "f316ec6025047133", "objects.sys_email.fields.bcc_addresses.label": "8674899b7b6d5126", "objects.sys_email.fields.cc_addresses.label": "02946d952cf15623", "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", 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 c856b2f2e04..0408fb8ca02 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 @@ -1154,19 +1154,36 @@ export const zhCNMetadataForms: NonNullable = helpText: "可用于快速搜索的字段名" }, resizable: { - label: "可调整大小" + label: "可调整大小", + helpText: "允许调整列宽" }, compactToolbar: { - label: "紧凑工具栏" + label: "紧凑工具栏", + helpText: "将分组 / 颜色 / 密度 / 隐藏字段收进同一个“视图设置”弹出框" }, rowHeight: { - label: "行高" + label: "行高", + helpText: "行高 / 密度设置" }, selection: { - label: "选择" + label: "选择", + helpText: "行选择配置" + }, + "selection.type": { + label: "类型", + helpText: "选择模式" }, pagination: { - label: "分页" + label: "分页", + helpText: "分页配置" + }, + "pagination.pageSize": { + label: "每页条数", + helpText: "每页显示的记录数。在没有分页器的视图(看板、画廊、时间线)上,它是取数上限,渲染器须做到两点:按此数限定取数;当筛选结果多于此数时,显示明显的截断提示,说明屏幕上并非全部记录" + }, + "pagination.pageSizeOptions": { + label: "每页条数选项", + helpText: "可选的每页条数" }, kanban: { label: "看板" @@ -1190,31 +1207,138 @@ export const zhCNMetadataForms: NonNullable = label: "用户筛选器", helpText: "快速筛选栏:控件样式(dropdown / tabs / toggle)+ 暴露的字段或标签页预设" }, + "userFilters.element": { + label: "控件样式", + helpText: "对象视图上的筛选控件样式:\"dropdown\"(按字段取值的筛选标签)。\"toggle\" 已弃用。\"tabs\" 仅用于页面——命名预设请使用 `listViews`。" + }, + "userFilters.fields": { + label: "筛选字段", + helpText: "作为快速筛选暴露的字段(dropdown / toggle 控件)" + }, + "userFilters.fields.field": { + label: "字段" + }, + "userFilters.fields.label": { + label: "显示名称" + }, + "userFilters.fields.type": { + label: "类型" + }, + "userFilters.fields.options": { + label: "选项" + }, + "userFilters.fields.showCount": { + label: "显示计数" + }, + "userFilters.fields.defaultValues": { + label: "默认值" + }, appearance: { label: "外观", helpText: "allowedVisualizations:允许用户切换的渲染器" }, + "appearance.showDescription": { + label: "显示描述", + helpText: "显示视图的描述文字" + }, + "appearance.allowedVisualizations": { + label: "允许的可视化", + helpText: "用户可在其间切换的可视化类型白名单(如 [\"grid\", \"gallery\", \"kanban\"])" + }, userActions: { label: "用户操作", helpText: "工具栏开关:排序 / 搜索 / 筛选 / 行高" }, + "userActions.sort": { + label: "排序", + helpText: "允许用户对记录排序" + }, + "userActions.search": { + label: "搜索", + helpText: "允许用户搜索记录" + }, + "userActions.filter": { + label: "筛选", + helpText: "允许用户筛选记录" + }, + "userActions.refresh": { + label: "刷新", + helpText: "允许用户从后端重新加载视图数据,无需整页刷新" + }, + "userActions.rowHeight": { + label: "行高", + helpText: "允许用户切换行高 / 密度" + }, + "userActions.group": { + label: "分组", + helpText: "允许用户在工具栏中更改记录分组。仅为开关——分组本身在视图级的 `grouping` 配置中设置。" + }, + "userActions.addRecordForm": { + label: "通过表单添加记录", + helpText: "通过表单而非行内方式添加记录" + }, + "userActions.editInline": { + label: "行内编辑", + helpText: "允许用户在行内编辑记录。默认开启:有权更新该对象的用户可直接在单元格内编辑,使用与该字段类型相符的控件(与表单所用相同),并受决定该对象是否可编辑的同一权限门控约束。声明 `editInline: false` 可让列表在原位只读。" + }, + "userActions.hideFields": { + label: "隐藏字段", + helpText: "允许用户在工具栏中隐藏 / 显示字段(即视图级 `hiddenFields` 列表背后的入口)。布尔开关——不同于记录详情组件的 `hideFields`,后者是要省略的字段名数组。默认开启:读者可自行隐藏和显示列;声明 `hideFields: false` 可收起该控件。" + }, + "userActions.rowColor": { + label: "行颜色", + helpText: "允许用户在工具栏中配置行着色。布尔开关——着色规则本身在视图级的 `rowColor` 配置中。默认关闭:行着色需显式开启。" + }, + "userActions.buttons": { + label: "按钮", + helpText: "要在工具栏中显示的自定义操作按钮 ID" + }, addRecord: { - label: "添加记录" + label: "添加记录", + helpText: "添加记录入口配置" }, "addRecord.enabled": { - label: "已启用" + label: "已启用", + helpText: "显示添加记录入口——必填:`true` 显示“添加”按钮,`false` 声明关闭" }, "addRecord.position": { - label: "位置" + label: "位置", + helpText: "添加记录按钮的位置:`top`(工具栏,默认)、`bottom`(列表下方)或 `both`" }, showRecordCount: { - label: "显示记录数" + label: "显示记录数", + helpText: "在列表底部显示记录数" }, navigation: { - label: "导航" + label: "导航", + helpText: "点击条目时的导航配置(默认为抽屉,也可为 page、new_window 或 none)" + }, + "navigation.mode": { + label: "模式" + }, + "navigation.preventNavigation": { + label: "禁止导航", + helpText: "完全禁用标准导航" + }, + "navigation.openNewTab": { + label: "在新标签页打开", + helpText: "强制在新标签页打开(适用于 page 模式)" + }, + "navigation.size": { + label: "尺寸", + helpText: "抽屉详情的浮层尺寸档位:'auto'(默认——渲染器按字段数与视口推导,AI 无需填写),或粗粒度覆盖 sm/md/lg/xl/full。优先使用它而非像素 `width`;page 模式会忽略它。" + }, + "navigation.width": { + label: "宽度", + helpText: "[已弃用 → size] 抽屉的像素 / 百分比宽度(如 \"600px\")。编写时无法预知客户端视口,因此选不出合适的像素宽度——请使用 `size` 档位。" }, sharing: { - label: "共享" + label: "共享", + helpText: "视图共享配置:谁锁定了视图配置" + }, + "sharing.lockedBy": { + label: "锁定者", + helpText: "锁定该视图配置的用户" } } }, From 5c71be15e5810e73a1c849aa264e386bb905005d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 05:58:28 +0000 Subject: [PATCH 3/6] fix(app-shell): zh-CN view panel reads Chinese on every row of its collapsed sections The console overlay copies the platform's zh-CN leaves for every newly declared view composite child, and the metadata-forms catalogs carry ja-JP and es-ES text for them too (the catalog-wide echo ledger reads zero in every translated locale). Pins: the overlay group in metadata-form-i18n.test.ts and a catalog test deriving its population from the form; the round-10 positive control moves 701 -> 731. Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv Co-authored-by: Claude --- .../es-ES.metadata-forms.generated.ts | 116 +++++++++--------- .../es-ES.source-hashes.generated.ts | 58 --------- .../ja-JP.metadata-forms.generated.ts | 116 +++++++++--------- .../ja-JP.source-hashes.generated.ts | 58 --------- ...ect-lifecycle-panel-echo-decisions.test.ts | 6 +- .../view-composite-children.test.ts | 91 ++++++++++++++ .../metadata-admin/metadata-form-i18n.test.ts | 70 +++++++++++ .../metadata-admin/metadata-form-zh-cn.ts | 55 +++++++-- 8 files changed, 326 insertions(+), 244 deletions(-) create mode 100644 packages/platform-objects/src/apps/translations/view-composite-children.test.ts 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 61747fff76a..aa8bc3b4029 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 @@ -1155,35 +1155,35 @@ export const esESMetadataForms: NonNullable = }, resizable: { label: "Redimensionable", - helpText: "Enable column resizing" + helpText: "Permite cambiar el ancho de las columnas" }, compactToolbar: { label: "Barra compacta", - helpText: "Collapse Group/Color/Density/Hide-fields into a single View settings popover" + helpText: "Agrupa Grupo / Color / Densidad / Ocultar campos en un único menú emergente de «Configuración de la vista»" }, rowHeight: { label: "Altura de fila", - helpText: "Row height / density setting" + helpText: "Ajuste de altura / densidad de las filas" }, selection: { label: "Selección", - helpText: "Row selection configuration" + helpText: "Configuración de la selección de filas" }, "selection.type": { - label: "Type", - helpText: "Selection mode" + label: "Tipo de selección", + helpText: "Modo de selección" }, pagination: { label: "Paginación", - helpText: "Pagination configuration" + helpText: "Configuración de la paginación" }, "pagination.pageSize": { - label: "Page Size", - helpText: "Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set" + label: "Tamaño de página", + helpText: "Número de registros por página. En una vista sin paginador (kanban, galería, línea de tiempo) es el límite de carga, y el renderizador debe hacer dos cosas: limitar la carga a este número y, cuando el conjunto filtrado sea mayor, mostrar un aviso visible de truncamiento que indique que lo que hay en pantalla no es el conjunto completo" }, "pagination.pageSizeOptions": { - label: "Page Size Options", - helpText: "Available page size options" + label: "Opciones de tamaño de página", + helpText: "Tamaños de página disponibles" }, kanban: { label: "Configuración Kanban" @@ -1208,12 +1208,12 @@ export const esESMetadataForms: NonNullable = helpText: "Barra de filtros rápidos: estilo de elemento (dropdown / tabs / toggle) + campos expuestos o preajustes de pestañas" }, "userFilters.element": { - label: "Element", - helpText: "Filter control style on object views: \"dropdown\" (per-field value chips). \"toggle\" is deprecated. \"tabs\" is page-only — use `listViews` for named presets." + label: "Estilo del control", + helpText: "Estilo del control de filtro en vistas de objeto: \"dropdown\" (chips de valores por campo). \"toggle\" está obsoleto. \"tabs\" es solo para páginas: usa `listViews` para los preajustes con nombre." }, "userFilters.fields": { - label: "Fields", - helpText: "Fields exposed as quick filters (dropdown/toggle elements)" + label: "Campos de filtro", + helpText: "Campos expuestos como filtros rápidos (controles dropdown / toggle)" }, "userFilters.fields.field": { label: "Campo" @@ -1238,107 +1238,107 @@ export const esESMetadataForms: NonNullable = helpText: "allowedVisualizations: qué renderizadores pueden alternar los usuarios" }, "appearance.showDescription": { - label: "Show Description", - helpText: "Show the view description text" + label: "Mostrar descripción", + helpText: "Muestra el texto de descripción de la vista" }, "appearance.allowedVisualizations": { - label: "Allowed Visualizations", - helpText: "Whitelist of visualization types users can switch between (e.g. [\"grid\", \"gallery\", \"kanban\"])" + label: "Visualizaciones permitidas", + helpText: "Lista de tipos de visualización entre los que los usuarios pueden cambiar (p. ej. [\"grid\", \"gallery\", \"kanban\"])" }, userActions: { label: "Acciones de usuario", helpText: "Conmutadores de la barra de herramientas: ordenar / buscar / filtrar / altura de fila" }, "userActions.sort": { - label: "Sort", - helpText: "Allow users to sort records" + label: "Ordenar", + helpText: "Permite a los usuarios ordenar los registros" }, "userActions.search": { - label: "Search", - helpText: "Allow users to search records" + label: "Buscar", + helpText: "Permite a los usuarios buscar registros" }, "userActions.filter": { - label: "Filter", - helpText: "Allow users to filter records" + label: "Filtrar", + helpText: "Permite a los usuarios filtrar los registros" }, "userActions.refresh": { - label: "Refresh", - helpText: "Allow users to reload the view data from the backend without a full page reload" + label: "Actualizar", + helpText: "Permite a los usuarios volver a cargar los datos de la vista desde el servidor sin recargar toda la página" }, "userActions.rowHeight": { - label: "Row Height", - helpText: "Allow users to toggle row height/density" + label: "Altura de fila", + helpText: "Permite a los usuarios cambiar la altura / densidad de las filas" }, "userActions.group": { - label: "Group", - helpText: "Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block." + label: "Agrupar", + helpText: "Permite a los usuarios cambiar la agrupación de registros desde la barra de herramientas. Solo es un interruptor: la agrupación en sí se configura en el bloque `grouping` de la vista." }, "userActions.addRecordForm": { - label: "Add Record Form", - helpText: "Add records through a form instead of inline" + label: "Agregar registro con formulario", + helpText: "Agrega registros mediante un formulario en lugar de en línea" }, "userActions.editInline": { - label: "Edit Inline", - helpText: "Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place." + label: "Edición en línea", + helpText: "Permite a los usuarios editar registros en línea. Activado por defecto: un usuario que puede actualizar el objeto edita una celda en su sitio con el control adecuado al tipo del campo (el mismo que usa el formulario), bajo la misma comprobación de permisos que ya decide si el objeto es editable. Declara `editInline: false` para que la lista sea de solo lectura en su sitio." }, "userActions.hideFields": { - label: "Hide Fields", - helpText: "Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control." + label: "Ocultar campos", + helpText: "Permite a los usuarios ocultar / mostrar campos desde la barra de herramientas (la función detrás de la lista `hiddenFields` de la vista). Interruptor booleano, distinto del `hideFields` del componente de detalles del registro, que es un array de nombres de campo que se omiten. Activado por defecto: cada lector oculta y muestra columnas para sí mismo; declara `hideFields: false` para retirar el control." }, "userActions.rowColor": { - label: "Row Color", - helpText: "Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in." + label: "Color de fila", + helpText: "Permite a los usuarios configurar el coloreado de filas desde la barra de herramientas. Interruptor booleano: las reglas de color están en el bloque `rowColor` de la vista. Desactivado por defecto: el coloreado de filas es opcional." }, "userActions.buttons": { - label: "Buttons", - helpText: "Custom action button IDs to show in the toolbar" + label: "Botones", + helpText: "ID de los botones de acción personalizados que se muestran en la barra de herramientas" }, addRecord: { label: "Agregar registro", - helpText: "Add record entry point configuration" + helpText: "Configuración del punto de entrada para agregar registros" }, "addRecord.enabled": { label: "Habilitado", - helpText: "Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off" + helpText: "Muestra el punto de entrada para agregar registros. OBLIGATORIO: `true` dibuja el botón Agregar y `false` lo declara desactivado" }, "addRecord.position": { label: "Posición", - helpText: "Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both`" + helpText: "Posición del botón para agregar registros: `top` (la barra de herramientas, por defecto), `bottom` (debajo de la lista) o `both`" }, showRecordCount: { label: "Mostrar recuento de registros", - helpText: "Show record count at the bottom of the list" + helpText: "Muestra el recuento de registros al pie de la lista" }, navigation: { label: "Navegación", - helpText: "Configuration for item click navigation (drawer by default, page, new_window or none)" + helpText: "Configuración de la navegación al hacer clic en un elemento (panel lateral por defecto; también page, new_window o none)" }, "navigation.mode": { - label: "Mode" + label: "Modo" }, "navigation.preventNavigation": { - label: "Prevent Navigation", - helpText: "Disable standard navigation entirely" + label: "Impedir navegación", + helpText: "Desactiva por completo la navegación estándar" }, "navigation.openNewTab": { - label: "Open New Tab", - helpText: "Force open in new tab (applies to page mode)" + label: "Abrir en pestaña nueva", + helpText: "Fuerza la apertura en una pestaña nueva (se aplica al modo page)" }, "navigation.size": { - label: "Size", - helpText: "Overlay size bucket for the drawer detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it." + label: "Tamaño", + helpText: "Categoría de tamaño del panel de detalle: 'auto' (por defecto: el renderizador lo deriva del número de campos y del área visible; la IA no escribe nada) o un ajuste aproximado sm/md/lg/xl/full. Prefiérelo al `width` en píxeles; el modo page lo ignora." }, "navigation.width": { - label: "Width", - helpText: "[DEPRECATED → size] Pixel/percent width of the drawer (e.g. \"600px\"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket." + label: "Ancho", + helpText: "[OBSOLETO → size] Ancho en píxeles / porcentaje del panel lateral (p. ej. \"600px\"). Al crear la vista no se conoce el área visible del cliente, así que no se puede elegir un ancho en píxeles: usa la categoría `size`." }, sharing: { label: "Compartición", - helpText: "View sharing configuration: who locked the view configuration" + helpText: "Configuración de uso compartido de la vista: quién bloqueó su configuración" }, "sharing.lockedBy": { - label: "Locked By", - helpText: "User who locked the view configuration" + label: "Bloqueado por", + helpText: "Usuario que bloqueó la configuración de la vista" } } }, 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 43fd5149c9e..4086c3c4669 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 @@ -18,64 +18,6 @@ */ export const esESGeneratedSourceHashes: Readonly> = { - "metadataForms.view.fields.addRecord.enabled.helpText": "d5ddec5558f800c5", - "metadataForms.view.fields.addRecord.helpText": "f799b27dd56271e7", - "metadataForms.view.fields.addRecord.position.helpText": "5ab594c22382a907", - "metadataForms.view.fields.appearance.allowedVisualizations.helpText": "6bc4ed77538231a6", - "metadataForms.view.fields.appearance.allowedVisualizations.label": "cd66b3b2fba2ab6f", - "metadataForms.view.fields.appearance.showDescription.helpText": "03a16d9590306528", - "metadataForms.view.fields.appearance.showDescription.label": "ae53a515325846d0", - "metadataForms.view.fields.compactToolbar.helpText": "c5e34d8f95fb5382", - "metadataForms.view.fields.navigation.helpText": "8b7f58dcdd7cfc25", - "metadataForms.view.fields.navigation.mode.label": "74dde5013435abdc", - "metadataForms.view.fields.navigation.openNewTab.helpText": "5779e8754f2625d7", - "metadataForms.view.fields.navigation.openNewTab.label": "5c49a752788679bc", - "metadataForms.view.fields.navigation.preventNavigation.helpText": "4cf351bc5e780666", - "metadataForms.view.fields.navigation.preventNavigation.label": "6a51a7fe68a1ef8b", - "metadataForms.view.fields.navigation.size.helpText": "0c54496e69c5dc0d", - "metadataForms.view.fields.navigation.size.label": "41f9ade673bbbc10", - "metadataForms.view.fields.navigation.width.helpText": "18acfadac0e77c3b", - "metadataForms.view.fields.navigation.width.label": "99a50f2407f41c32", - "metadataForms.view.fields.pagination.helpText": "823ad0f6e9ccc45f", - "metadataForms.view.fields.pagination.pageSize.helpText": "6d5dd7640a1e7096", - "metadataForms.view.fields.pagination.pageSize.label": "21cf295f387dffb5", - "metadataForms.view.fields.pagination.pageSizeOptions.helpText": "30b3c8b5475ac828", - "metadataForms.view.fields.pagination.pageSizeOptions.label": "979e0594bd63ebb7", - "metadataForms.view.fields.resizable.helpText": "697608babf720579", - "metadataForms.view.fields.rowHeight.helpText": "b3fd4fcdd8a002fb", - "metadataForms.view.fields.selection.helpText": "9ffc2ff5ab629fd1", - "metadataForms.view.fields.selection.type.helpText": "e5dd21f1530d0d2f", - "metadataForms.view.fields.selection.type.label": "901bb552898b2cab", - "metadataForms.view.fields.sharing.helpText": "29a59b2fb250f3d5", - "metadataForms.view.fields.sharing.lockedBy.helpText": "351bd9e70614d07f", - "metadataForms.view.fields.sharing.lockedBy.label": "93a358a9cfb08f52", - "metadataForms.view.fields.showRecordCount.helpText": "eb9ae575ad0a0efd", - "metadataForms.view.fields.userActions.addRecordForm.helpText": "a018a68bfadbbec9", - "metadataForms.view.fields.userActions.addRecordForm.label": "9f75b8792c5a9ca6", - "metadataForms.view.fields.userActions.buttons.helpText": "1f2a3891a103f76d", - "metadataForms.view.fields.userActions.buttons.label": "213eb6a492478539", - "metadataForms.view.fields.userActions.editInline.helpText": "18352e04aba3b9cf", - "metadataForms.view.fields.userActions.editInline.label": "d0d5c3713f3280db", - "metadataForms.view.fields.userActions.filter.helpText": "54accbfbacddbafb", - "metadataForms.view.fields.userActions.filter.label": "802895ec427fc6af", - "metadataForms.view.fields.userActions.group.helpText": "5ab0a0e6af342edb", - "metadataForms.view.fields.userActions.group.label": "588222bcabb4c16e", - "metadataForms.view.fields.userActions.hideFields.helpText": "a04c7e48a82df497", - "metadataForms.view.fields.userActions.hideFields.label": "76f0012fdee673b5", - "metadataForms.view.fields.userActions.refresh.helpText": "611966483c1d8aa5", - "metadataForms.view.fields.userActions.refresh.label": "1b63ae2e002930a6", - "metadataForms.view.fields.userActions.rowColor.helpText": "d34658e12c5b91b2", - "metadataForms.view.fields.userActions.rowColor.label": "6d6bd56442a27c9c", - "metadataForms.view.fields.userActions.rowHeight.helpText": "622077b9bf4e2edd", - "metadataForms.view.fields.userActions.rowHeight.label": "4ac338620533198d", - "metadataForms.view.fields.userActions.search.helpText": "32a9fcaaaf8e8fb4", - "metadataForms.view.fields.userActions.search.label": "e09aaf5d7e06f528", - "metadataForms.view.fields.userActions.sort.helpText": "624d1ed3daeddaf6", - "metadataForms.view.fields.userActions.sort.label": "4c590a0ed0c02eff", - "metadataForms.view.fields.userFilters.element.helpText": "a41896f00679ee07", - "metadataForms.view.fields.userFilters.element.label": "e480e4fe57239c98", - "metadataForms.view.fields.userFilters.fields.helpText": "d312e9549dc8c5cd", - "metadataForms.view.fields.userFilters.fields.label": "f316ec6025047133", "objects.sys_email.fields.cc_addresses.label": "02946d952cf15623", "objects.sys_email.fields.error.label": "786fed84bd8d5a32", "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", 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 d2856094e7e..4d0771e3e35 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 @@ -1155,35 +1155,35 @@ export const jaJPMetadataForms: NonNullable = }, resizable: { label: "サイズ変更可", - helpText: "Enable column resizing" + helpText: "列幅の変更を許可します" }, compactToolbar: { label: "コンパクトツールバー", - helpText: "Collapse Group/Color/Density/Hide-fields into a single View settings popover" + helpText: "グループ / 色 / 密度 / フィールド非表示を 1 つの「ビュー設定」ポップオーバーにまとめます" }, rowHeight: { label: "行の高さ", - helpText: "Row height / density setting" + helpText: "行の高さ / 密度の設定" }, selection: { label: "選択", - helpText: "Row selection configuration" + helpText: "行選択の設定" }, "selection.type": { - label: "Type", - helpText: "Selection mode" + label: "種類", + helpText: "選択モード" }, pagination: { label: "ページネーション", - helpText: "Pagination configuration" + helpText: "ページネーションの設定" }, "pagination.pageSize": { - label: "Page Size", - helpText: "Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set" + label: "ページサイズ", + helpText: "1 ページあたりのレコード数。ページャーのないビュー(カンバン、ギャラリー、タイムライン)では取得の上限となり、レンダラーは 2 つのことを行う必要があります:この数で取得を制限すること、そして絞り込み結果がこの数を超える場合は、画面に表示されているのが全件ではないことを示す切り捨て表示を目に見える形で出すこと" }, "pagination.pageSizeOptions": { - label: "Page Size Options", - helpText: "Available page size options" + label: "ページサイズの選択肢", + helpText: "選択できるページサイズ" }, kanban: { label: "カンバン" @@ -1208,12 +1208,12 @@ export const jaJPMetadataForms: NonNullable = helpText: "クイックフィルターバー:要素スタイル(dropdown / tabs / toggle)+ 公開フィールドまたはタブプリセット" }, "userFilters.element": { - label: "Element", - helpText: "Filter control style on object views: \"dropdown\" (per-field value chips). \"toggle\" is deprecated. \"tabs\" is page-only — use `listViews` for named presets." + label: "コントロールの種類", + helpText: "オブジェクトビューでのフィルターコントロールの種類:\"dropdown\"(フィールドごとの値チップ)。\"toggle\" は非推奨です。\"tabs\" はページ専用です——名前付きプリセットには `listViews` を使用してください。" }, "userFilters.fields": { - label: "Fields", - helpText: "Fields exposed as quick filters (dropdown/toggle elements)" + label: "フィルター項目", + helpText: "クイックフィルターとして公開するフィールド(dropdown / toggle コントロール)" }, "userFilters.fields.field": { label: "フィールド" @@ -1238,107 +1238,107 @@ export const jaJPMetadataForms: NonNullable = helpText: "allowedVisualizations:ユーザーが切り替え可能なレンダラー" }, "appearance.showDescription": { - label: "Show Description", - helpText: "Show the view description text" + label: "説明を表示", + helpText: "ビューの説明文を表示します" }, "appearance.allowedVisualizations": { - label: "Allowed Visualizations", - helpText: "Whitelist of visualization types users can switch between (e.g. [\"grid\", \"gallery\", \"kanban\"])" + label: "許可する表示形式", + helpText: "ユーザーが切り替えられる表示形式の許可リスト(例:[\"grid\", \"gallery\", \"kanban\"])" }, userActions: { label: "ユーザーアクション", helpText: "ツールバーの切り替え:並べ替え / 検索 / フィルター / 行の高さ" }, "userActions.sort": { - label: "Sort", - helpText: "Allow users to sort records" + label: "並び替え", + helpText: "ユーザーにレコードの並び替えを許可します" }, "userActions.search": { - label: "Search", - helpText: "Allow users to search records" + label: "検索", + helpText: "ユーザーにレコードの検索を許可します" }, "userActions.filter": { - label: "Filter", - helpText: "Allow users to filter records" + label: "フィルター", + helpText: "ユーザーにレコードの絞り込みを許可します" }, "userActions.refresh": { - label: "Refresh", - helpText: "Allow users to reload the view data from the backend without a full page reload" + label: "再読み込み", + helpText: "ページ全体を再読み込みせずに、バックエンドからビューのデータを再取得することをユーザーに許可します" }, "userActions.rowHeight": { - label: "Row Height", - helpText: "Allow users to toggle row height/density" + label: "行の高さ", + helpText: "ユーザーに行の高さ / 密度の切り替えを許可します" }, "userActions.group": { - label: "Group", - helpText: "Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block." + label: "グループ化", + helpText: "ツールバーからレコードのグループ化を変更することをユーザーに許可します。切り替えのみです——グループ化そのものはビュー単位の `grouping` ブロックで設定します。" }, "userActions.addRecordForm": { - label: "Add Record Form", - helpText: "Add records through a form instead of inline" + label: "フォームでレコードを追加", + helpText: "インラインではなくフォームでレコードを追加します" }, "userActions.editInline": { - label: "Edit Inline", - helpText: "Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place." + label: "インライン編集", + helpText: "ユーザーにレコードのインライン編集を許可します。既定でオン:オブジェクトを更新できるユーザーは、そのフィールドの型に合ったウィジェット(フォームと同じコントロール)でセルをその場で編集でき、オブジェクトが編集可能かどうかを決める同じ権限ゲートが適用されます。`editInline: false` を宣言すると、一覧はその場では読み取り専用になります。" }, "userActions.hideFields": { - label: "Hide Fields", - helpText: "Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control." + label: "フィールドを非表示", + helpText: "ツールバーからフィールドの表示 / 非表示を切り替えることをユーザーに許可します(ビュー単位の `hiddenFields` リストを操作する機能)。ブール値の切り替えです——レコード詳細コンポーネントの `hideFields`(省略するフィールド名の配列)とは別物です。既定でオン:閲覧者が自分で列を表示・非表示にできます。`hideFields: false` を宣言するとこのコントロールを出しません。" }, "userActions.rowColor": { - label: "Row Color", - helpText: "Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in." + label: "行の色", + helpText: "ツールバーから行の色分けを設定することをユーザーに許可します。ブール値の切り替えです——色分けのルール自体はビュー単位の `rowColor` ブロックにあります。既定でオフ:行の色分けはオプトインです。" }, "userActions.buttons": { - label: "Buttons", - helpText: "Custom action button IDs to show in the toolbar" + label: "ボタン", + helpText: "ツールバーに表示するカスタムアクションボタンの ID" }, addRecord: { label: "レコードを追加", - helpText: "Add record entry point configuration" + helpText: "レコード追加の入口の設定" }, "addRecord.enabled": { label: "有効", - helpText: "Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off" + helpText: "レコード追加の入口を表示します——必須:`true` で追加ボタンを表示し、`false` でオフを宣言します" }, "addRecord.position": { label: "位置", - helpText: "Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both`" + helpText: "レコード追加ボタンの位置:`top`(ツールバー、既定)、`bottom`(一覧の下)、または `both`" }, showRecordCount: { label: "レコード数を表示", - helpText: "Show record count at the bottom of the list" + helpText: "一覧の下部にレコード件数を表示します" }, navigation: { label: "ナビゲーション", - helpText: "Configuration for item click navigation (drawer by default, page, new_window or none)" + helpText: "項目をクリックしたときのナビゲーション設定(既定はドロワー。page、new_window、none も指定可)" }, "navigation.mode": { - label: "Mode" + label: "モード" }, "navigation.preventNavigation": { - label: "Prevent Navigation", - helpText: "Disable standard navigation entirely" + label: "ナビゲーションを無効化", + helpText: "標準のナビゲーションを完全に無効にします" }, "navigation.openNewTab": { - label: "Open New Tab", - helpText: "Force open in new tab (applies to page mode)" + label: "新しいタブで開く", + helpText: "常に新しいタブで開きます(page モードに適用)" }, "navigation.size": { - label: "Size", - helpText: "Overlay size bucket for the drawer detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it." + label: "サイズ", + helpText: "ドロワー詳細のオーバーレイサイズ区分:'auto'(既定——レンダラーがフィールド数とビューポートから決定し、AI は何も書きません)、または大まかな指定 sm/md/lg/xl/full。ピクセル指定の `width` よりこちらを優先してください。page モードでは無視されます。" }, "navigation.width": { - label: "Width", - helpText: "[DEPRECATED → size] Pixel/percent width of the drawer (e.g. \"600px\"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket." + label: "幅", + helpText: "[非推奨 → size] ドロワーのピクセル / パーセント幅(例:\"600px\")。作成時にはクライアントのビューポートが分からないため、ピクセル幅は決められません——`size` 区分を使用してください。" }, sharing: { label: "共有", - helpText: "View sharing configuration: who locked the view configuration" + helpText: "ビューの共有設定:誰がビューの設定をロックしたか" }, "sharing.lockedBy": { - label: "Locked By", - helpText: "User who locked the view configuration" + label: "ロックしたユーザー", + helpText: "ビューの設定をロックしたユーザー" } } }, 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 5018ab573b3..1582af3d0ee 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 @@ -18,64 +18,6 @@ */ export const jaJPGeneratedSourceHashes: Readonly> = { - "metadataForms.view.fields.addRecord.enabled.helpText": "d5ddec5558f800c5", - "metadataForms.view.fields.addRecord.helpText": "f799b27dd56271e7", - "metadataForms.view.fields.addRecord.position.helpText": "5ab594c22382a907", - "metadataForms.view.fields.appearance.allowedVisualizations.helpText": "6bc4ed77538231a6", - "metadataForms.view.fields.appearance.allowedVisualizations.label": "cd66b3b2fba2ab6f", - "metadataForms.view.fields.appearance.showDescription.helpText": "03a16d9590306528", - "metadataForms.view.fields.appearance.showDescription.label": "ae53a515325846d0", - "metadataForms.view.fields.compactToolbar.helpText": "c5e34d8f95fb5382", - "metadataForms.view.fields.navigation.helpText": "8b7f58dcdd7cfc25", - "metadataForms.view.fields.navigation.mode.label": "74dde5013435abdc", - "metadataForms.view.fields.navigation.openNewTab.helpText": "5779e8754f2625d7", - "metadataForms.view.fields.navigation.openNewTab.label": "5c49a752788679bc", - "metadataForms.view.fields.navigation.preventNavigation.helpText": "4cf351bc5e780666", - "metadataForms.view.fields.navigation.preventNavigation.label": "6a51a7fe68a1ef8b", - "metadataForms.view.fields.navigation.size.helpText": "0c54496e69c5dc0d", - "metadataForms.view.fields.navigation.size.label": "41f9ade673bbbc10", - "metadataForms.view.fields.navigation.width.helpText": "18acfadac0e77c3b", - "metadataForms.view.fields.navigation.width.label": "99a50f2407f41c32", - "metadataForms.view.fields.pagination.helpText": "823ad0f6e9ccc45f", - "metadataForms.view.fields.pagination.pageSize.helpText": "6d5dd7640a1e7096", - "metadataForms.view.fields.pagination.pageSize.label": "21cf295f387dffb5", - "metadataForms.view.fields.pagination.pageSizeOptions.helpText": "30b3c8b5475ac828", - "metadataForms.view.fields.pagination.pageSizeOptions.label": "979e0594bd63ebb7", - "metadataForms.view.fields.resizable.helpText": "697608babf720579", - "metadataForms.view.fields.rowHeight.helpText": "b3fd4fcdd8a002fb", - "metadataForms.view.fields.selection.helpText": "9ffc2ff5ab629fd1", - "metadataForms.view.fields.selection.type.helpText": "e5dd21f1530d0d2f", - "metadataForms.view.fields.selection.type.label": "901bb552898b2cab", - "metadataForms.view.fields.sharing.helpText": "29a59b2fb250f3d5", - "metadataForms.view.fields.sharing.lockedBy.helpText": "351bd9e70614d07f", - "metadataForms.view.fields.sharing.lockedBy.label": "93a358a9cfb08f52", - "metadataForms.view.fields.showRecordCount.helpText": "eb9ae575ad0a0efd", - "metadataForms.view.fields.userActions.addRecordForm.helpText": "a018a68bfadbbec9", - "metadataForms.view.fields.userActions.addRecordForm.label": "9f75b8792c5a9ca6", - "metadataForms.view.fields.userActions.buttons.helpText": "1f2a3891a103f76d", - "metadataForms.view.fields.userActions.buttons.label": "213eb6a492478539", - "metadataForms.view.fields.userActions.editInline.helpText": "18352e04aba3b9cf", - "metadataForms.view.fields.userActions.editInline.label": "d0d5c3713f3280db", - "metadataForms.view.fields.userActions.filter.helpText": "54accbfbacddbafb", - "metadataForms.view.fields.userActions.filter.label": "802895ec427fc6af", - "metadataForms.view.fields.userActions.group.helpText": "5ab0a0e6af342edb", - "metadataForms.view.fields.userActions.group.label": "588222bcabb4c16e", - "metadataForms.view.fields.userActions.hideFields.helpText": "a04c7e48a82df497", - "metadataForms.view.fields.userActions.hideFields.label": "76f0012fdee673b5", - "metadataForms.view.fields.userActions.refresh.helpText": "611966483c1d8aa5", - "metadataForms.view.fields.userActions.refresh.label": "1b63ae2e002930a6", - "metadataForms.view.fields.userActions.rowColor.helpText": "d34658e12c5b91b2", - "metadataForms.view.fields.userActions.rowColor.label": "6d6bd56442a27c9c", - "metadataForms.view.fields.userActions.rowHeight.helpText": "622077b9bf4e2edd", - "metadataForms.view.fields.userActions.rowHeight.label": "4ac338620533198d", - "metadataForms.view.fields.userActions.search.helpText": "32a9fcaaaf8e8fb4", - "metadataForms.view.fields.userActions.search.label": "e09aaf5d7e06f528", - "metadataForms.view.fields.userActions.sort.helpText": "624d1ed3daeddaf6", - "metadataForms.view.fields.userActions.sort.label": "4c590a0ed0c02eff", - "metadataForms.view.fields.userFilters.element.helpText": "a41896f00679ee07", - "metadataForms.view.fields.userFilters.element.label": "e480e4fe57239c98", - "metadataForms.view.fields.userFilters.fields.helpText": "d312e9549dc8c5cd", - "metadataForms.view.fields.userFilters.fields.label": "f316ec6025047133", "objects.sys_email.fields.bcc_addresses.label": "8674899b7b6d5126", "objects.sys_email.fields.cc_addresses.label": "02946d952cf15623", "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", diff --git a/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts b/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts index da392eb5285..57fc31219e4 100644 --- a/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts +++ b/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts @@ -1259,7 +1259,11 @@ describe('#19403 round 10 — the verdicts, on the live bundles', () => { // 701 since #29442: the flow form's variables repeater offers // `objectName` (a start input declared a record id) — one row label, // authored in all three locales. - expect(translated.length, `${locale} positive control`).toBe(701); + // 731 since #30267: the view form spells out the children of its + // `selection` / `pagination` / `userFilters` (with the `fields` repeater's + // six row properties) / `appearance` / `userActions` / `navigation` / + // `sharing` composites — thirty row labels, authored in all three locales. + expect(translated.length, `${locale} positive control`).toBe(731); } // ⭐ DARK — the blindness, executable. On a synthetic two-locale catalog the // all-three predicate returns 0 while the per-locale one returns 1, so the diff --git a/packages/platform-objects/src/apps/translations/view-composite-children.test.ts b/packages/platform-objects/src/apps/translations/view-composite-children.test.ts new file mode 100644 index 00000000000..9f88f1b71fd --- /dev/null +++ b/packages/platform-objects/src/apps/translations/view-composite-children.test.ts @@ -0,0 +1,91 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// #30267 — the catalogs name every child the view form declares under its +// composites, and zh-CN carries its own text for each of them. +// +// `view.form.ts` used to declare `selection`, `pagination`, `userFilters`, +// `appearance`, `userActions`, `navigation` and `sharing` as composites with +// no children. `os i18n extract` walks a form field's DECLARED `fields` only, +// so none of those rows had a `metadataForms.view.fields['.']` +// key, and the zh-CN Studio read ~70 English rows in the four collapsed +// sections of the view properties panel. The form now spells the children +// out; this file pins the catalog half: +// +// • every declared child (repeater rows included) is a key in every +// catalog — the population is DERIVED from the form, so a child declared +// tomorrow is red here on the day it lands until the catalogs are +// regenerated; +// • the `en` leaf IS the form's text — label (or its humanized path) and +// helpText; +// • zh-CN never echoes the `en` source on any of these leaves. +// +// ja-JP and es-ES are deliberately NOT held to the no-echo rule here beyond +// the six `userFilters.fields` row properties (`repeater-row-properties.test.ts` +// owns those): the card is the zh-CN panel, and their other new leaves are +// the extractor's en fills. + +import { describe, it, expect } from 'vitest'; +import { viewForm } from '@objectstack/spec/ui'; + +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'; + +type FormField = { field?: string; label?: string; helpText?: string; fields?: FormField[] }; + +const COMPOSITES = ['selection', 'pagination', 'userFilters', 'appearance', 'userActions', 'addRecord', 'navigation', 'sharing']; + +/** `{ path, field }` for every child declared under the composites above, recursively. */ +function declaredChildren(): Array<{ path: string; f: FormField }> { + const out: Array<{ path: string; f: FormField }> = []; + const walk = (fields: FormField[] | undefined, prefix: string) => { + for (const f of fields ?? []) { + if (!f?.field) continue; + const path = `${prefix}.${f.field}`; + out.push({ path, f }); + walk(f.fields, path); + } + }; + for (const section of (viewForm as { sections?: Array<{ fields?: FormField[] }> }).sections ?? []) { + for (const f of section.fields ?? []) if (f?.field && COMPOSITES.includes(f.field)) walk(f.fields, f.field); + } + return out; +} + +const CHILDREN = declaredChildren(); +const en = (enMetadataForms as Record).view.fields as Record>; +const zh = (zhCNMetadataForms as Record).view.fields as Record>; + +describe('#30267 — view composite children in the metadata-forms catalogs', () => { + it('derives a real population (control)', () => { + const paths = CHILDREN.map((c) => c.path); + expect(paths).toContain('pagination.pageSize'); + expect(paths).toContain('userFilters.fields.showCount'); + expect(paths).toContain('sharing.lockedBy'); + expect(paths).not.toContain('navigation.view'); + expect(CHILDREN.length).toBeGreaterThan(30); + }); + + for (const [name, forms] of [['en', enMetadataForms], ['zh-CN', zhCNMetadataForms], ['ja-JP', jaJPMetadataForms], ['es-ES', esESMetadataForms]] as const) { + it(`${name}: names every declared child`, () => { + const fields = (forms as Record).view.fields; + const missing = CHILDREN.filter(({ path, f }) => typeof fields[path]?.label !== 'string' || (f.helpText !== undefined && typeof fields[path]?.helpText !== 'string')); + expect(missing.map((m) => m.path)).toEqual([]); + }); + } + + it('en: each helpText leaf IS the form\'s declared hint', () => { + for (const { path, f } of CHILDREN) { + if (f.helpText !== undefined) expect(en[path].helpText, path).toBe(f.helpText); + if (f.label !== undefined) expect(en[path].label, path).toBe(f.label); + } + }); + + it('zh-CN carries its own text for every leaf, never a copy of the en source', () => { + const echoes = CHILDREN.flatMap(({ path }) => + Object.keys(en[path]).filter((leaf) => zh[path]?.[leaf] === en[path][leaf]).map((leaf) => `${path}.${leaf}`), + ); + expect(echoes).toEqual([]); + }); +}); diff --git a/ui/packages/app-shell/src/views/metadata-admin/metadata-form-i18n.test.ts b/ui/packages/app-shell/src/views/metadata-admin/metadata-form-i18n.test.ts index 4cc55a4ca01..1922aa46124 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/metadata-form-i18n.test.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/metadata-form-i18n.test.ts @@ -248,3 +248,73 @@ describe('every form type Studio renders reads in zh-CN (objectstack#30039)', () expect(text).toContain('仪表盘'); }); }); + +describe('the view panel’s expanded sections read in zh-CN, every row (objectstack#30267)', () => { + /** The four collapsed sections the card names, by their slugged names’ zh headings. */ + const SECTIONS = ['表格选项', '分页', '终端用户控制', '导航与共享'] as const; + const CJK = /[一-鿿]/; + + /** Every field and declared child (recursively) under the named sections. */ + function rows(form: unknown, headings: readonly string[]): Array<{ path: string; f: FormField }> { + const out: Array<{ path: string; f: FormField }> = []; + const walk = (fields: FormField[] | undefined, prefix: string) => { + for (const f of fields ?? []) { + if (!f || typeof f !== 'object' || !f.field) continue; + const path = prefix ? `${prefix}.${f.field}` : f.field; + out.push({ path, f }); + walk(f.fields, path); + } + }; + for (const s of sections(form)) if (headings.includes(s.label ?? '')) walk(s.fields, ''); + return out; + } + + function child(form: unknown, parent: string, name: string): FormField | undefined { + return fieldByName(form, parent)?.fields?.find((c) => c.field === name); + } + + it('every row label and every hint under the four sections is Chinese', () => { + const zh = getViewForm('zh-CN'); + const all = rows(zh, SECTIONS); + // Control — the walk reached the sections and their children, so a missing + // section cannot pass by vacuity. + expect(sections(zh).map((s) => s.label)).toEqual(expect.arrayContaining([...SECTIONS])); + expect(all.length).toBeGreaterThan(40); + const english = all.flatMap(({ path, f }) => [ + ...(typeof f.label === 'string' && CJK.test(f.label) ? [] : [`${path}.label = ${JSON.stringify(f.label)}`]), + ...(f.helpText === undefined || CJK.test(f.helpText) ? [] : [`${path}.helpText = ${JSON.stringify(f.helpText)}`]), + ]); + expect(english).toEqual([]); + }); + + it('a sample of each composite’s children reads the platform’s words', () => { + const zh = getViewForm('zh-CN'); + expect(child(zh, 'selection', 'type')).toMatchObject({ label: '类型', helpText: '选择模式' }); + expect(child(zh, 'pagination', 'pageSize')?.label).toBe('每页条数'); + expect(child(zh, 'pagination', 'pageSizeOptions')?.helpText).toBe('可选的每页条数'); + expect(child(zh, 'userFilters', 'element')?.label).toBe('控件样式'); + expect(child(zh, 'userFilters', 'fields')?.fields?.map((r) => r.label)).toEqual(['字段', '显示名称', '类型', '选项', '显示计数', '默认值']); + expect(child(zh, 'appearance', 'allowedVisualizations')?.label).toBe('允许的可视化'); + expect(child(zh, 'userActions', 'editInline')?.label).toBe('行内编辑'); + expect(child(zh, 'navigation', 'size')?.label).toBe('尺寸'); + expect(child(zh, 'sharing', 'lockedBy')).toMatchObject({ label: '锁定者', helpText: '锁定该视图配置的用户' }); + expect(fieldByName(zh, 'resizable')?.helpText).toBe('允许调整列宽'); + }); + + it('the panel no longer offers the retired navigation.view / sharing.type rows', () => { + const zh = getViewForm('zh-CN'); + expect(fieldByName(zh, 'navigation')?.fields?.map((c) => c.field)).not.toContain('view'); + expect(fieldByName(zh, 'sharing')?.fields?.map((c) => c.field)).toEqual(['lockedBy']); + }); + + it('an English console reads the spec’s own English for the same rows — the control', () => { + const en = getViewForm('en'); + expect(child(en, 'pagination', 'pageSizeOptions')?.helpText).toBe('Available page size options'); + expect(child(en, 'sharing', 'lockedBy')?.helpText).toBe('User who locked the view configuration'); + expect(child(en, 'userFilters', 'fields')?.fields?.map((r) => r.label)).toEqual(['Field', 'Label', 'Type', 'Options', 'Show Count', 'Default Values']); + expect(fieldByName(en, 'resizable')?.helpText).toBe('Enable column resizing'); + const cjk = rows(en, ['Table options', 'Pagination', 'End-user controls', 'Navigation & sharing']) + .filter(({ f }) => CJK.test(`${f.label ?? ''}${f.helpText ?? ''}`)); + expect(cjk).toEqual([]); + }); +}); diff --git a/ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts b/ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts index ffefa543ff5..f9bb0536f3a 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts @@ -402,11 +402,14 @@ export const ZH_CN_SPEC_FORMS: NonNullable = { 'sort.field': { label: '字段' }, 'sort.order': { label: '排序方向' }, searchableFields: { label: '可搜索字段', helpText: '可用于快速搜索的字段名' }, - resizable: { label: '可调整大小' }, - compactToolbar: { label: '紧凑工具栏' }, - rowHeight: { label: '行高' }, - selection: { label: '选择' }, - pagination: { label: '分页' }, + resizable: { label: '可调整大小', helpText: '允许调整列宽' }, + compactToolbar: { label: '紧凑工具栏', helpText: '将分组 / 颜色 / 密度 / 隐藏字段收进同一个“视图设置”弹出框' }, + rowHeight: { label: '行高', helpText: '行高 / 密度设置' }, + selection: { label: '选择', helpText: '行选择配置' }, + 'selection.type': { label: '类型', helpText: '选择模式' }, + pagination: { label: '分页', helpText: '分页配置' }, + 'pagination.pageSize': { label: '每页条数', helpText: '每页显示的记录数。在没有分页器的视图(看板、画廊、时间线)上,它是取数上限,渲染器须做到两点:按此数限定取数;当筛选结果多于此数时,显示明显的截断提示,说明屏幕上并非全部记录' }, + 'pagination.pageSizeOptions': { label: '每页条数选项', helpText: '可选的每页条数' }, kanban: { label: '看板' }, calendar: { label: '日历' }, gantt: { label: '甘特图' }, @@ -414,14 +417,44 @@ export const ZH_CN_SPEC_FORMS: NonNullable = { timeline: { label: '时间线' }, chart: { label: '图表' }, userFilters: { label: '用户筛选器', helpText: '快速筛选栏:控件样式(dropdown / tabs / toggle)+ 暴露的字段或标签页预设' }, + // #30267: every composite below now declares its children in + // `view.form.ts`, so each child row is named here (all of them, per + // the ⛔ composite rule at the top of this file). + 'userFilters.element': { label: '控件样式', helpText: '对象视图上的筛选控件样式:"dropdown"(按字段取值的筛选标签)。"toggle" 已弃用。"tabs" 仅用于页面——命名预设请使用 `listViews`。' }, + 'userFilters.fields': { label: '筛选字段', helpText: '作为快速筛选暴露的字段(dropdown / toggle 控件)' }, + 'userFilters.fields.field': { label: '字段' }, + 'userFilters.fields.label': { label: '显示名称' }, + 'userFilters.fields.type': { label: '类型' }, + 'userFilters.fields.options': { label: '选项' }, + 'userFilters.fields.showCount': { label: '显示计数' }, + 'userFilters.fields.defaultValues': { label: '默认值' }, appearance: { label: '外观', helpText: 'allowedVisualizations:允许用户切换的渲染器' }, + 'appearance.showDescription': { label: '显示描述', helpText: '显示视图的描述文字' }, + 'appearance.allowedVisualizations': { label: '允许的可视化', helpText: '用户可在其间切换的可视化类型白名单(如 ["grid", "gallery", "kanban"])' }, userActions: { label: '用户操作', helpText: '工具栏开关:排序 / 搜索 / 筛选 / 行高' }, - addRecord: { label: '添加记录' }, - 'addRecord.enabled': { label: '已启用' }, - 'addRecord.position': { label: '位置' }, - showRecordCount: { label: '显示记录数' }, - navigation: { label: '导航' }, - sharing: { label: '共享' }, + 'userActions.sort': { label: '排序', helpText: '允许用户对记录排序' }, + 'userActions.search': { label: '搜索', helpText: '允许用户搜索记录' }, + 'userActions.filter': { label: '筛选', helpText: '允许用户筛选记录' }, + 'userActions.refresh': { label: '刷新', helpText: '允许用户从后端重新加载视图数据,无需整页刷新' }, + 'userActions.rowHeight': { label: '行高', helpText: '允许用户切换行高 / 密度' }, + 'userActions.group': { label: '分组', helpText: '允许用户在工具栏中更改记录分组。仅为开关——分组本身在视图级的 `grouping` 配置中设置。' }, + 'userActions.addRecordForm': { label: '通过表单添加记录', helpText: '通过表单而非行内方式添加记录' }, + 'userActions.editInline': { label: '行内编辑', helpText: '允许用户在行内编辑记录。默认开启:有权更新该对象的用户可直接在单元格内编辑,使用与该字段类型相符的控件(与表单所用相同),并受决定该对象是否可编辑的同一权限门控约束。声明 `editInline: false` 可让列表在原位只读。' }, + 'userActions.hideFields': { label: '隐藏字段', helpText: '允许用户在工具栏中隐藏 / 显示字段(即视图级 `hiddenFields` 列表背后的入口)。布尔开关——不同于记录详情组件的 `hideFields`,后者是要省略的字段名数组。默认开启:读者可自行隐藏和显示列;声明 `hideFields: false` 可收起该控件。' }, + 'userActions.rowColor': { label: '行颜色', helpText: '允许用户在工具栏中配置行着色。布尔开关——着色规则本身在视图级的 `rowColor` 配置中。默认关闭:行着色需显式开启。' }, + 'userActions.buttons': { label: '按钮', helpText: '要在工具栏中显示的自定义操作按钮 ID' }, + addRecord: { label: '添加记录', helpText: '添加记录入口配置' }, + 'addRecord.enabled': { label: '已启用', helpText: '显示添加记录入口——必填:`true` 显示“添加”按钮,`false` 声明关闭' }, + 'addRecord.position': { label: '位置', helpText: '添加记录按钮的位置:`top`(工具栏,默认)、`bottom`(列表下方)或 `both`' }, + showRecordCount: { label: '显示记录数', helpText: '在列表底部显示记录数' }, + navigation: { label: '导航', helpText: '点击条目时的导航配置(默认为抽屉,也可为 page、new_window 或 none)' }, + 'navigation.mode': { label: '模式' }, + 'navigation.preventNavigation': { label: '禁止导航', helpText: '完全禁用标准导航' }, + 'navigation.openNewTab': { label: '在新标签页打开', helpText: '强制在新标签页打开(适用于 page 模式)' }, + 'navigation.size': { label: '尺寸', helpText: '抽屉详情的浮层尺寸档位:\'auto\'(默认——渲染器按字段数与视口推导,AI 无需填写),或粗粒度覆盖 sm/md/lg/xl/full。优先使用它而非像素 `width`;page 模式会忽略它。' }, + 'navigation.width': { label: '宽度', helpText: '[已弃用 → size] 抽屉的像素 / 百分比宽度(如 "600px")。编写时无法预知客户端视口,因此选不出合适的像素宽度——请使用 `size` 档位。' }, + sharing: { label: '共享', helpText: '视图共享配置:谁锁定了视图配置' }, + 'sharing.lockedBy': { label: '锁定者', helpText: '锁定该视图配置的用户' }, }, }, app: { From 0b5f0648a443bc17ae0435734233e6108fd4247d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 06:09:36 +0000 Subject: [PATCH 4/6] fix(spec): keep the quick-filter fields repeater an inline table Before it was enumerated, userFilters.fields rendered as the panel's auto-detected master-detail table with a required marker on Field. The declared repeater now asks for the grid layout and marks the required row property, so the panel keeps that look with translated column heads. Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv Co-authored-by: Claude --- packages/spec/src/ui/view-form-composite-children.test.ts | 2 ++ packages/spec/src/ui/view.form.ts | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/spec/src/ui/view-form-composite-children.test.ts b/packages/spec/src/ui/view-form-composite-children.test.ts index 9e1e19eb69a..6e19437f4ce 100644 --- a/packages/spec/src/ui/view-form-composite-children.test.ts +++ b/packages/spec/src/ui/view-form-composite-children.test.ts @@ -103,6 +103,8 @@ describe('#30267 — view form composites declare their children', () => { it('userFilters.fields is a repeater whose row labels are the item schema titles', () => { const repeater = (formField('userFilters').fields ?? []).find((c) => c.field === 'fields'); expect(repeater?.type).toBe('repeater'); + // The inline table it rendered as before it was enumerated. + expect((repeater as { widget?: string } | undefined)?.widget).toBe('grid'); const items = LIST.properties.userFilters.properties.fields.items.properties as Record; const rows = repeater?.fields ?? []; expect(rows.map((r) => r.field).sort()).toEqual(liveKeys(items).sort()); diff --git a/packages/spec/src/ui/view.form.ts b/packages/spec/src/ui/view.form.ts index 83a0a69eec8..a78c3cceb3b 100644 --- a/packages/spec/src/ui/view.form.ts +++ b/packages/spec/src/ui/view.form.ts @@ -206,12 +206,16 @@ export const viewForm = defineForm({ { field: 'fields', type: 'repeater', + // `grid`: the inline table this array of objects rendered as + // before it was enumerated (the panel's auto-detected + // master-detail table), now with translatable column heads. + widget: 'grid', helpText: 'Fields exposed as quick filters (dropdown/toggle elements)', // Row-property names: every authorable row property, `label` // equal to the item schema's `.meta({ title })`, as `columns` // above does. fields: [ - { field: 'field', label: 'Field' }, + { field: 'field', label: 'Field', required: true }, { field: 'label', label: 'Label' }, { field: 'type', label: 'Type' }, { field: 'options', label: 'Options' }, From d92e5a4ed13e2b2d2ca95be174ad78805e2e6283 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 06:11:55 +0000 Subject: [PATCH 5/6] chore: changeset for the zh-CN view panel collapsed-section rows Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv Co-authored-by: Claude --- .changeset/30267-view-panel-zh-composite-rows.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/30267-view-panel-zh-composite-rows.md diff --git a/.changeset/30267-view-panel-zh-composite-rows.md b/.changeset/30267-view-panel-zh-composite-rows.md new file mode 100644 index 00000000000..652f3c97234 --- /dev/null +++ b/.changeset/30267-view-panel-zh-composite-rows.md @@ -0,0 +1,15 @@ +--- +'@objectstack/spec': patch +'@objectstack/platform-objects': patch +'@object-ui/app-shell': patch +--- + +fix: on a zh-CN console every row of the view properties panel's collapsed sections (表格选项 / 分页 / 终端用户控制 / 导航与共享) reads in Chinese + +Clause-②: no +Metadata-protocol: none + +- `viewForm` (`@objectstack/spec/ui`) now spells out the children of its `selection`, `pagination`, `userFilters`, `appearance`, `userActions`, `navigation` and `sharing` composites, and gives the rows that had none a hint. Each declared hint is the schema description the panel already showed, so an English console reads the same text; `os i18n extract` now emits a `metadataForms.view.fields['.']` key per row. +- `userFilters.fields` is enumerated as an inline-table repeater, and `UserFilterFieldSchema`'s six properties carry a `.meta({ title })` (Field / Label / Type / Options / Show Count / Default Values) — the English column heads, unchanged. +- The panel no longer offers `navigation.view` and `sharing.type`: both are `retiredKey()` tombstones that the schema-derived child list used to render as editable rows. A stored value is untouched by a panel save (the composite editor spreads the block it was given). +- The platform metadata-forms catalogs carry zh-CN, ja-JP and es-ES text for every new row, and the console's zh-CN overlay copies the zh-CN rows. From 890e350cc96644a72b51f3d8fb0036fe848f98e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 06:23:10 +0000 Subject: [PATCH 6/6] test(spec): ledger the page-only userFilters keys the view form does not offer The reconciliation walk resolves view.userFilters to the page-shaped UserFiltersSchema; the list arm the view form lays out refuses tabs, showAllRecords and allowAddTab, so they get omit rows. Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv Co-authored-by: Claude --- .../system/metadata-form-zod-reconciliation.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts b/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts index 54bed50042e..942c7e998bc 100644 --- a/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts +++ b/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts @@ -198,6 +198,18 @@ const LEDGER: ReadonlyArray = [ key: 'sourceView', why: '@deprecated legacy named-view inheritance, honored at runtime as a fallback but deliberately not offered to new authors — a page defines columns/sort/filterBy directly (ADR-0047 revised)', }, + // #30267 — `view.form.ts` now spells out `userFilters`' children. This walk + // resolves the block through the view union to the page-shaped + // `UserFiltersSchema`; the list arm the view form lays out takes + // `ObjectUserFiltersSchema`, which omits these three page-only keys and + // refuses them on an object view. + ...(['tabs', 'showAllRecords', 'allowAddTab'] as const).map((key): OmitEntry => ({ + kind: 'omit', + type: 'view', + path: 'userFilters', + key, + why: "page-only — a list view's `userFilters` is `ObjectUserFiltersSchema`, which omits this key and refuses it on an object view (named presets on an object view are `listViews` entries); the view form lays out that list arm, so a control here would offer a value the save rejects", + })), { kind: 'omit', type: 'object',