diff --git a/.changeset/25922-studio-i18n-inspectors.md b/.changeset/25922-studio-i18n-inspectors.md new file mode 100644 index 00000000000..28e72123ac2 --- /dev/null +++ b/.changeset/25922-studio-i18n-inspectors.md @@ -0,0 +1,10 @@ +--- +'@object-ui/i18n': patch +--- + +feat(i18n): the Studio inspectors' page block, action, dataset, flow node, app navigation and dashboard widget strings move into the `studio` namespace + +Clause-②: no +Metadata-protocol: none + +- **Second slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The 520 keys under `engine.inspector.pageBlock.*`, `engine.inspector.action.*`, `engine.inspector.dataset.*`, `engine.inspector.flowNode.*`, `engine.inspector.appNav.*` and `engine.inspector.widget.*` now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged in both `en` and `zh`. Studio reads the same strings; a Chinese viewer sees these labels in English for the moment the `zh` catalogue takes to load, as with the first slice. No export changes. diff --git a/ui/packages/app-shell/src/views/metadata-admin/i18n.ts b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts index ca2c38c0292..332ac56609d 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -489,58 +489,9 @@ const ENGINE_STRINGS_EN: Record = { 'engine.edit.exitFullscreen': 'Exit fullscreen', 'engine.edit.designMode': 'Design', 'engine.edit.previewMode': 'Preview', - 'engine.inspector.widget.kind': 'Widget', - 'engine.inspector.widget.close': 'Close widget', - 'engine.inspector.widget.title': 'Title', - 'engine.inspector.widget.type': 'Type', - 'engine.inspector.widget.object': 'Data Source (Object)', - 'engine.inspector.widget.valueField': 'Value Field', - 'engine.inspector.widget.categoryField': 'Category Field', - 'engine.inspector.widget.aggregate': 'Aggregate', - 'engine.inspector.widget.color': 'Color Variant', - 'engine.inspector.widget.width': 'Width', - 'engine.inspector.widget.height': 'Height', - 'engine.inspector.widget.remove': 'Remove widget', // Marks an object that exists only as an unpublished draft in the object // picker, so the author can tell it apart from a published sibling. 'engine.inspector.draftSuffix': '(draft)', - // Dataset binding (ADR-0021) — governed cross-object semantic layer. - 'engine.inspector.widget.datasetSection': 'Dataset binding', - 'engine.inspector.widget.dataset': 'Dataset', - 'engine.inspector.widget.datasetPlaceholder': 'e.g. sales_pipeline', - 'engine.inspector.widget.datasetHint': - 'Bind a governed dataset. When set, it takes precedence over the inline object query above and the widget renders via the dataset (consistent, cross-object, RLS-enforced).', - 'engine.inspector.widget.dimensions': 'Dimensions', - 'engine.inspector.widget.dimensionsPlaceholder': 'e.g. stage, region (comma-separated)', - 'engine.inspector.widget.dimensionsHint': - 'Dataset dimension names to group by. Leave empty for a single-value KPI metric.', - 'engine.inspector.widget.values': 'Values (measures)', - 'engine.inspector.widget.valuesPlaceholder': 'e.g. revenue, deal_count (comma-separated)', - 'engine.inspector.widget.valuesHint': 'Dataset measure names to show.', - // Dashboard filter bindings (framework#2501) - 'engine.inspector.widget.filterBindingsSection': 'Dashboard filter bindings', - 'engine.inspector.widget.filterBindingsHint': - 'Map each dashboard-level filter to one of this widget’s own fields, or untick Apply to opt the widget out. Empty = the filter’s own field.', - 'engine.inspector.widget.filterBindingApply': 'Apply', - 'engine.inspector.widget.filterBindingDefault': 'Default ({field})', - 'engine.inspector.widget.filterBindingField': 'Bound field for {filter}', - 'engine.inspector.widget.filterBindingReset': 'Reset', - // objectui#10748 — the widget-type select's options (keyed by the stored - // `type`, which never moves), the untitled-widget fallback and the two - // empty-state messages. - 'engine.inspector.widget.type.metric': 'KPI Metric', - 'engine.inspector.widget.type.bar': 'Bar Chart', - 'engine.inspector.widget.type.horizontalBar': 'Horizontal Bar', - 'engine.inspector.widget.type.line': 'Line Chart', - 'engine.inspector.widget.type.area': 'Area Chart', - 'engine.inspector.widget.type.pie': 'Pie Chart', - 'engine.inspector.widget.type.donut': 'Donut Chart', - 'engine.inspector.widget.type.funnel': 'Funnel', - 'engine.inspector.widget.type.table': 'Table', - 'engine.inspector.widget.type.pivot': 'Pivot Table', - 'engine.inspector.widget.untitledN': 'Widget {n}', - 'engine.inspector.widget.unsupportedSelection': 'Unsupported selection kind: {kind}', - 'engine.inspector.widget.removed': 'The selected widget was removed from the draft.', // objectui#10804 (objectui#10805 folded in) — the dashboard add-widget picker // (`AddWidgetPicker`): its search box, empty text, category headings and the // type names it lists (`WIDGET_TYPE_META[].labelKey`). Display only: the new @@ -575,69 +526,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.dashboardPreview.saveTitle': 'Save title', 'engine.dashboardPreview.renameWidget': 'Rename widget', 'engine.dashboardPreview.clearSelection': 'Clear selection', - // Flow node inspector - 'engine.inspector.flowNode.kind': 'Node', - 'engine.inspector.flowNode.close': 'Close node', - 'engine.inspector.flowNode.id': 'ID', - // objectui#11827 — the ID field's inline refusal of a rename that cannot land; - // the field shows the stored id again, so each says nothing was renamed. - 'engine.inspector.flowNode.idRequired': 'Not renamed: a node needs an id.', - 'engine.inspector.flowNode.idTaken': 'Not renamed: another node in this flow already has the id "{id}".', - 'engine.inspector.flowNode.idEdgeNamed': 'Not renamed: an edge still names "{id}", a node that is no longer in the flow. Remove or reconnect that edge first.', - // objectui#11838 — the rename carries every expression reference to the node - // through the parsers; these name the ones it cannot carry. `{refs}` lists - // each as ` › : `. - 'engine.inspector.flowNode.idRefsUnparsed': 'Not renamed: these expressions read "{id}" but do not parse, so the rename cannot carry them: {refs}. Fix them first.', - 'engine.inspector.flowNode.idRefsAmbiguous': 'Not renamed: "{name}" is also the name of a variable in this flow, so these expressions could read either and cannot be carried: {refs}. Rename the variable first, or edit them by hand.', - // objectui#11838 — a removal cannot carry a boundary event's host or an - // expression root, so it is refused while one names the node (the Remove - // node button and the canvas Delete key both show this). - 'engine.inspector.flowNode.removeRefused': 'Not removed: these still name "{id}" and would name a node that no longer exists: {refs}. Change or remove them first.', - 'engine.inspector.flowNode.label': 'Label', - 'engine.inspector.flowNode.type': 'Node Type', - 'engine.inspector.flowNode.configuration': 'Configuration', - 'engine.inspector.flowNode.config': 'Config (JSON)', - 'engine.inspector.flowNode.advanced': 'Advanced (JSON)', - 'engine.inspector.flowNode.advancedHint': 'Optional custom keys not covered by the form above — most flows don\u2019t need this.', - // objectui#6499 — a dependent field whose controller is off still renders, - // because a stored value is never hidden. These name that state instead of - // letting it read as live configuration. - 'engine.inspector.flowNode.inactiveRetained': 'Kept, not in effect — its controlling field is off. The value stays saved until you clear it.', - 'engine.inspector.flowNode.inactiveRetainedOrphan': 'Kept, not in effect — nothing activates this field. The value stays saved so it is not lost silently.', - 'engine.inspector.flowNode.inactiveRetainedClear': 'Clear value', - // objectui#12779 — an interval or once cadence on a start node, drawn read-only. - 'engine.inspector.flowNode.cadenceLabel': 'Schedule', - 'engine.inspector.flowNode.cadenceEveryMs': 'Every {n} ms', - 'engine.inspector.flowNode.cadenceEverySeconds': 'Every {n} s', - 'engine.inspector.flowNode.cadenceEveryMinutes': 'Every {n} min', - 'engine.inspector.flowNode.cadenceEveryHours': 'Every {n} h', - 'engine.inspector.flowNode.cadenceEveryDays': 'Every {n} d', - 'engine.inspector.flowNode.cadenceOnce': 'Once, at {at}', - 'engine.inspector.flowNode.cadenceReadOnly': 'This flow runs on an interval or at a set time, not on a cron. The designer edits cron schedules only, so this schedule is shown as stored and kept unchanged.', - 'engine.inspector.flowNode.noConfig': 'No configuration needed for this node type.', - 'engine.inspector.flowNode.nestedIdHint': 'A node inside a container region keeps its id here — rename it in the container’s Advanced JSON.', - // objectui#10748 — the nested-node breadcrumb's accessible name, and the - // Advanced (JSON) box's refusal of a value that parses but is not an object. - 'engine.inspector.flowNode.nestedLocation': 'nested node location', - 'engine.inspector.flowNode.advancedNotObject': 'Must be a JSON object', - 'engine.inspector.flowNode.kv.add': 'Add entry', - 'engine.inspector.flowNode.kv.key': 'Key', - 'engine.inspector.flowNode.kv.value': 'Value', - 'engine.inspector.flowNode.kv.remove': 'Remove entry', - 'engine.inspector.flowNode.kv.empty': 'No entries yet.', - // objectui#7588 — the per-value text / expression toggle on a `value`-role - // map (the assignment node's `assignments`); see `flow-value-envelope.ts`. - 'engine.inspector.flowNode.kv.asExpression': 'Write as a CEL expression', - 'engine.inspector.flowNode.kv.expressionPlaceholder': 'CEL expression, e.g. joinNonEmpty(…)', - 'engine.inspector.flowNode.list.add': 'Add item', - 'engine.inspector.flowNode.list.item': 'Value', - 'engine.inspector.flowNode.list.remove': 'Remove item', - 'engine.inspector.flowNode.list.empty': 'No items yet.', - 'engine.inspector.flowNode.branch.add': 'Add branch', - 'engine.inspector.flowNode.branch.fromEdges': 'This decision routes on its connections’ conditions. Each card is one connection; edit its condition on the connection.', - 'engine.inspector.flowNode.branch.openEdge': 'Edit connection', - 'engine.inspector.flowNode.branch.noCondition': 'No condition', - 'engine.inspector.flowNode.remove': 'Remove node', // Flow edge (connection) inspector 'engine.inspector.flowEdge.kind': 'Connection', 'engine.inspector.flowEdge.close': 'Close connection', @@ -675,58 +563,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.inspector.workflowAction.name': 'Name', 'engine.inspector.workflowAction.config': 'Config (JSON)', 'engine.inspector.workflowAction.remove': 'Remove action', - // App nav inspector - 'engine.inspector.appNav.kind': 'Nav Item', - 'engine.inspector.appNav.close': 'Close nav item', - 'engine.inspector.appNav.label': 'Label', - 'engine.inspector.appNav.icon': 'Icon', - 'engine.inspector.appNav.remove': 'Remove nav item', - 'engine.inspector.appNav.typeField': 'Type', - 'engine.inspector.appNav.type.object': 'Object', - 'engine.inspector.appNav.type.page': 'Page', - 'engine.inspector.appNav.type.dashboard': 'Dashboard', - 'engine.inspector.appNav.type.report': 'Report', - 'engine.inspector.appNav.type.url': 'Link (URL)', - 'engine.inspector.appNav.type.group': 'Group', - 'engine.inspector.appNav.object': 'Object', - 'engine.inspector.appNav.targetMode': 'Landing', - 'engine.inspector.appNav.mode.default': 'Default view (workspace)', - 'engine.inspector.appNav.mode.view': 'Named view', - 'engine.inspector.appNav.mode.record': 'Record deep-link', - 'engine.inspector.appNav.mode.filters': 'Filtered slice (/data)', - 'engine.inspector.appNav.view': 'View', - 'engine.inspector.appNav.recordId': 'Record ID', - 'engine.inspector.appNav.recordIdHint': 'Supports {current_user_id} and {current_org_id} template variables.', - 'engine.inspector.appNav.recordMode': 'Open mode', - 'engine.inspector.appNav.recordModeView': 'View', - 'engine.inspector.appNav.recordModeEdit': 'Edit', - 'engine.inspector.appNav.filters': 'URL filters', - 'engine.inspector.appNav.filtersField': 'Field', - 'engine.inspector.appNav.filtersValue': 'Value', - 'engine.inspector.appNav.filtersAdd': 'Add condition', - 'engine.inspector.appNav.filtersRemove': 'Remove condition', - 'engine.inspector.appNav.filtersHint': 'One-off slice carried in the URL — promote it to a named view when it becomes curated and reused.', - 'engine.inspector.appNav.url': 'URL', - 'engine.inspector.appNav.urlTarget': 'Open in', - 'engine.inspector.appNav.urlTargetSelf': 'Same tab', - 'engine.inspector.appNav.urlTargetBlank': 'New tab', - 'engine.inspector.appNav.preview': 'Resolved link', - // objectui#11790 — the Studio nav editor's Type choice offers every member - // of the spec's nav-item union; these are the members, and the pickers' - // texts, the rows above did not carry. - 'engine.inspector.appNav.type.action': 'Action', - 'engine.inspector.appNav.type.component': 'Component', - 'engine.inspector.appNav.type.doc': 'Documentation', - 'engine.inspector.appNav.type.separator': 'Separator', - 'engine.inspector.appNav.choose': '— Choose —', - 'engine.inspector.appNav.noTargets': 'There is nothing of this type to link to yet.', - 'engine.inspector.appNav.unboundHint': 'Until a target is chosen, this item stays here but is left out of the saved navigation.', - 'engine.inspector.appNav.actionHint': 'Only actions not bound to an object can run from the menu.', - 'engine.inspector.appNav.docPage': 'Doc page', - 'engine.inspector.appNav.book': 'Book', - 'engine.inspector.appNav.docHint': 'Choose a doc page, a book, or both.', - 'engine.inspector.appNav.groupHint': 'A group is a titled section of the menu. Items are not nested under it from this editor.', - 'engine.inspector.appNav.separatorHint': 'A separator is a divider: it has no label and opens nothing.', // View column inspector 'engine.inspector.viewColumn.kind': 'Column', 'engine.inspector.viewColumn.close': 'Close column', @@ -809,217 +645,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.inspector.view.type.timeline': 'Timeline', 'engine.inspector.view.type.map': 'Map', 'engine.inspector.view.type.chart': 'Chart', - // Page block inspector - 'engine.inspector.pageBlock.kind': 'Block', - 'engine.inspector.pageBlock.close': 'Close block', - 'engine.inspector.pageBlock.type': 'Type', - 'engine.inspector.pageBlock.id': 'ID', - 'engine.inspector.pageBlock.className': 'Class names', - 'engine.inspector.pageBlock.visibleWhen': 'Visible when (CEL)', - 'engine.inspector.pageBlock.properties': 'Properties', - 'engine.inspector.pageBlock.advanced': 'Advanced', - 'engine.inspector.pageBlock.remove': 'Remove block', - 'engine.inspector.pageBlock.outlineLabel': 'Blocks', - // Chrome of the panel's own list/JSON editors (#3963) — NOT block-config - // data. These live beside the keys above (not under `.field.`, which is - // reserved for keys derived from a BLOCK_CONFIG position) because they are - // one shared control repeated across every list field, with no per-field - // wording. `list.*` mirrors `engine.inspector.flowNode.list.*`, the same - // editor shape in a sibling panel. - 'engine.inspector.pageBlock.list.add': 'Add', - 'engine.inspector.pageBlock.list.remove': 'Remove', - 'engine.inspector.pageBlock.list.removeItem': 'Remove item', - 'engine.inspector.pageBlock.objectPlaceholder': 'snake_case object', - 'engine.inspector.pageBlock.fieldPlaceholder': 'field name', - // Page block inspector — curated property labels (#3913) and the prose - // placeholders (#3979). - // These are the `label` / `addLabel` / option-label values of - // `previews/block-config.ts`; that file stores the KEY and the inspector - // resolves it through `t()` at render. Key shape is derived from the label's - // position in BLOCK_CONFIG and pinned by - // `previews/__tests__/block-config-i18n.test.ts` — see that file's header. - // - // `placeholder.*` is the same derivation for a field's input hint, and it is - // deliberately only PART of that column: a placeholder holding an example - // VALUE (`20`, `https://…`, a JSON sample) stays a literal in block-config and - // has no key here, because translating it would corrupt what the author is - // being told to type. `block-config.ts` declares which kind each one is. - 'engine.inspector.pageBlock.field.object-grid.objectName': 'Object', - 'engine.inspector.pageBlock.field.object-grid.columns': 'Columns', - 'engine.inspector.pageBlock.field.object-grid.pageSize': 'Page size', - 'engine.inspector.pageBlock.field.object-form.objectName': 'Object', - 'engine.inspector.pageBlock.field.object-form.mode': 'Mode', - 'engine.inspector.pageBlock.option.mode.create': 'Create', - 'engine.inspector.pageBlock.option.mode.edit': 'Edit', - 'engine.inspector.pageBlock.option.mode.view': 'View', - 'engine.inspector.pageBlock.field.object-form.formType': 'Form type', - 'engine.inspector.pageBlock.option.formType.simple': 'Simple', - 'engine.inspector.pageBlock.option.formType.tabbed': 'Tabbed', - 'engine.inspector.pageBlock.option.formType.wizard': 'Wizard', - 'engine.inspector.pageBlock.option.formType.split': 'Split', - 'engine.inspector.pageBlock.option.formType.drawer': 'Drawer', - 'engine.inspector.pageBlock.option.formType.modal': 'Modal', - 'engine.inspector.pageBlock.field.object-form.layout': 'Layout', - 'engine.inspector.pageBlock.option.layout.vertical': 'Vertical', - 'engine.inspector.pageBlock.option.layout.horizontal': 'Horizontal', - 'engine.inspector.pageBlock.field.object-form.columns': 'Field columns (1–4)', - 'engine.inspector.pageBlock.field.object-form.fields': 'Fields', - 'engine.inspector.pageBlock.field.object-form.title': 'Title', - 'engine.inspector.pageBlock.field.object-form.description': 'Description', - 'engine.inspector.pageBlock.field.object-metric.objectName': 'Object', - 'engine.inspector.pageBlock.field.object-metric.label': 'Label', - 'engine.inspector.pageBlock.field.object-metric.description': 'Description', - 'engine.inspector.pageBlock.field.object-metric.icon': 'Icon', - 'engine.inspector.pageBlock.placeholder.object-metric.icon': 'lucide icon name', - 'engine.inspector.pageBlock.field.object-metric.colorVariant': 'Color', - 'engine.inspector.pageBlock.option.colorVariant.default': 'Default', - 'engine.inspector.pageBlock.option.colorVariant.blue': 'Blue', - 'engine.inspector.pageBlock.option.colorVariant.teal': 'Teal', - 'engine.inspector.pageBlock.option.colorVariant.orange': 'Orange', - 'engine.inspector.pageBlock.option.colorVariant.purple': 'Purple', - 'engine.inspector.pageBlock.option.colorVariant.success': 'Success', - 'engine.inspector.pageBlock.option.colorVariant.warning': 'Warning', - 'engine.inspector.pageBlock.option.colorVariant.danger': 'Danger', - 'engine.inspector.pageBlock.field.object-metric.format': 'Format', - 'engine.inspector.pageBlock.option.format.number': 'Number', - 'engine.inspector.pageBlock.option.format.currency': 'Currency', - 'engine.inspector.pageBlock.option.format.percent': 'Percent', - 'engine.inspector.pageBlock.field.object-metric.prefix': 'Prefix', - 'engine.inspector.pageBlock.field.object-metric.suffix': 'Suffix', - 'engine.inspector.pageBlock.field.object-kanban.objectName': 'Object', - // `…object-kanban.groupField` was renamed to `…groupBy` with the control it - // labels (objectui#7772): the lane picker wrote a key `ObjectKanban.tsx` - // never reads and objectui#7322 retired by name, while the REQUIRED `groupBy` - // had no control. The English text is deliberately unchanged — the box means - // exactly what it always meant; only the key its position implies moved. - 'engine.inspector.pageBlock.field.object-kanban.groupBy': 'Group by field', - 'engine.inspector.pageBlock.field.object-kanban.titleField': 'Title field', - 'engine.inspector.pageBlock.field.object-kanban.cardFields': 'Card fields', - 'engine.inspector.pageBlock.field.object-kanban.limit': 'Limit', - 'engine.inspector.pageBlock.field.grid.columns': 'Columns', - 'engine.inspector.pageBlock.field.grid.gap': 'Gap', - 'engine.inspector.pageBlock.field.element:text.content': 'Content', - 'engine.inspector.pageBlock.placeholder.element:text.content': 'Text…', - 'engine.inspector.pageBlock.field.element:text.variant': 'Variant', - 'engine.inspector.pageBlock.option.variant.h1': 'Heading 1', - 'engine.inspector.pageBlock.option.variant.h2': 'Heading 2', - 'engine.inspector.pageBlock.option.variant.h3': 'Heading 3', - 'engine.inspector.pageBlock.option.variant.h4': 'Heading 4', - 'engine.inspector.pageBlock.option.variant.h5': 'Heading 5', - 'engine.inspector.pageBlock.option.variant.h6': 'Heading 6', - 'engine.inspector.pageBlock.option.variant.body': 'Body', - 'engine.inspector.pageBlock.option.variant.caption': 'Caption', - 'engine.inspector.pageBlock.option.variant.overline': 'Overline', - 'engine.inspector.pageBlock.field.element:text.align': 'Align', - 'engine.inspector.pageBlock.option.align.left': 'Left', - 'engine.inspector.pageBlock.option.align.center': 'Center', - 'engine.inspector.pageBlock.option.align.right': 'Right', - 'engine.inspector.pageBlock.field.element:image.src': 'Source URL', - 'engine.inspector.pageBlock.field.element:image.alt': 'Alt text', - 'engine.inspector.pageBlock.field.element:image.fit': 'Fit', - 'engine.inspector.pageBlock.option.fit.cover': 'Cover', - 'engine.inspector.pageBlock.option.fit.contain': 'Contain', - 'engine.inspector.pageBlock.option.fit.fill': 'Fill', - 'engine.inspector.pageBlock.field.element:definition-list.items': 'Items', - 'engine.inspector.pageBlock.add.element:definition-list.items': 'Add item', - 'engine.inspector.pageBlock.field.element:definition-list.items.term': 'Term', - 'engine.inspector.pageBlock.field.element:definition-list.items.description': 'Description', - 'engine.inspector.pageBlock.field.element:definition-list.columns': 'Columns (1 or 2)', - 'engine.inspector.pageBlock.field.element:definition-list.inline': 'Inline (term · description)', - 'engine.inspector.pageBlock.field.element:repeater.object': 'Object', - 'engine.inspector.pageBlock.field.element:repeater.titleField': 'Title field', - 'engine.inspector.pageBlock.field.element:repeater.fields': 'Fields', - 'engine.inspector.pageBlock.field.element:repeater.limit': 'Limit', - 'engine.inspector.pageBlock.field.element:repeater.emptyText': 'Empty text', - 'engine.inspector.pageBlock.field.element:repeater.divided': 'Dividers between rows', - 'engine.inspector.pageBlock.field.element:number.object': 'Object', - 'engine.inspector.pageBlock.field.element:number.field': 'Field', - 'engine.inspector.pageBlock.field.element:number.aggregate': 'Aggregate', - 'engine.inspector.pageBlock.option.aggregate.count': 'Count', - 'engine.inspector.pageBlock.option.aggregate.sum': 'Sum', - 'engine.inspector.pageBlock.option.aggregate.avg': 'Average', - 'engine.inspector.pageBlock.option.aggregate.min': 'Min', - 'engine.inspector.pageBlock.option.aggregate.max': 'Max', - 'engine.inspector.pageBlock.field.element:number.format': 'Format', - 'engine.inspector.pageBlock.field.element:number.prefix': 'Prefix', - 'engine.inspector.pageBlock.field.element:number.suffix': 'Suffix', - 'engine.inspector.pageBlock.field.element:button.label': 'Label', - 'engine.inspector.pageBlock.field.element:button.variant': 'Variant', - 'engine.inspector.pageBlock.option.variant.primary': 'Primary', - 'engine.inspector.pageBlock.option.variant.secondary': 'Secondary', - 'engine.inspector.pageBlock.option.variant.danger': 'Danger', - 'engine.inspector.pageBlock.option.variant.ghost': 'Ghost', - 'engine.inspector.pageBlock.option.variant.link': 'Link', - 'engine.inspector.pageBlock.field.element:button.size': 'Size', - 'engine.inspector.pageBlock.option.size.small': 'Small', - 'engine.inspector.pageBlock.option.size.medium': 'Medium', - 'engine.inspector.pageBlock.option.size.large': 'Large', - 'engine.inspector.pageBlock.field.element:button.icon': 'Icon', - 'engine.inspector.pageBlock.placeholder.element:button.icon': 'lucide icon name', - 'engine.inspector.pageBlock.field.element:button.action': 'Action', - 'engine.inspector.pageBlock.field.page:header.title': 'Title', - 'engine.inspector.pageBlock.field.page:header.subtitle': 'Subtitle', - // `…field.page:header.icon` and its placeholder retired with the spec key - // itself (objectstack#6946 / PR objectstack#7115, @objectstack/spec - // 17.0.0-rc.6). Their only reader was the canonical `page:header` icon field - // removed from `previews/block-config.ts`; a key kept past its field is dead - // vocabulary that the next author reads as a live surface. objectui#3829. - // `…field.page:header.breadcrumb` left the same way with the designer's - // breadcrumb toggle (objectui#11173; the spec key retires through - // objectstack#20758). - 'engine.inspector.pageBlock.field.page:card.title': 'Title', - 'engine.inspector.pageBlock.field.page:card.bordered': 'Bordered', - 'engine.inspector.pageBlock.field.page:tabs.items': 'Tabs', - 'engine.inspector.pageBlock.add.page:tabs.items': 'Add tab', - 'engine.inspector.pageBlock.field.page:tabs.items.value': 'Value', - 'engine.inspector.pageBlock.field.page:tabs.items.label': 'Label', - // `…field.page:accordion.title` and `…field.page:accordion.items.value` - // retired with their designer fields (objectui#5212): neither is read by - // `PageAccordionRenderer` nor declared by `PageAccordionProps`, so a key kept - // past its field is dead vocabulary the next author reads as a live surface. - 'engine.inspector.pageBlock.field.page:accordion.items': 'Sections', - 'engine.inspector.pageBlock.add.page:accordion.items': 'Add section', - 'engine.inspector.pageBlock.field.page:accordion.items.label': 'Label', - 'engine.inspector.pageBlock.field.record:related_list.objectName': 'Object', - 'engine.inspector.pageBlock.field.record:related_list.relationshipField': 'Relationship field', - 'engine.inspector.pageBlock.field.record:related_list.title': 'Title', - 'engine.inspector.pageBlock.field.record:related_list.limit': 'Limit', - 'engine.inspector.pageBlock.field.record:highlights.fields': 'Fields', - 'engine.inspector.pageBlock.field.record:details.sections': 'Sections', - 'engine.inspector.pageBlock.add.record:details.sections': 'Add section', - 'engine.inspector.pageBlock.field.record:details.sections.name': 'Name (i18n key)', - 'engine.inspector.pageBlock.placeholder.record:details.sections.name': 'snake_case, e.g. contact_info', - 'engine.inspector.pageBlock.field.record:details.sections.label': 'Label', - 'engine.inspector.pageBlock.field.record:details.sections.columns': 'Columns', - 'engine.inspector.pageBlock.field.record:details.sections.fields': 'Fields', - 'engine.inspector.pageBlock.field.record:alert.severity': 'Severity', - 'engine.inspector.pageBlock.option.severity.info': 'Info', - 'engine.inspector.pageBlock.option.severity.warning': 'Warning', - 'engine.inspector.pageBlock.option.severity.error': 'Error', - 'engine.inspector.pageBlock.option.severity.success': 'Success', - 'engine.inspector.pageBlock.field.record:alert.title': 'Title', - 'engine.inspector.pageBlock.field.record:alert.body': 'Body', - 'engine.inspector.pageBlock.field.record:alert.icon': 'Icon', - 'engine.inspector.pageBlock.placeholder.record:alert.icon': 'lucide icon name', - 'engine.inspector.pageBlock.field.record:alert.dismissible': 'Dismissible', - 'engine.inspector.pageBlock.field.record:path.statusField': 'Status field', - 'engine.inspector.pageBlock.field.record:path.stages': 'Stages', - 'engine.inspector.pageBlock.add.record:path.stages': 'Add stage', - 'engine.inspector.pageBlock.field.record:path.stages.value': 'Value', - 'engine.inspector.pageBlock.field.record:path.stages.label': 'Label', - 'engine.inspector.pageBlock.field.record:quick_actions.actionNames': 'Action names', - 'engine.inspector.pageBlock.placeholder.record:quick_actions.actionNames': 'action name', - 'engine.inspector.pageBlock.field.record:quick_actions.location': 'Location', - 'engine.inspector.pageBlock.option.location.record_header': 'Record header', - 'engine.inspector.pageBlock.option.location.record_more': 'Record more menu', - 'engine.inspector.pageBlock.option.location.record_section': 'Record section', - 'engine.inspector.pageBlock.option.location.record_related': 'Record related', - 'engine.inspector.pageBlock.option.location.list_toolbar': 'List toolbar', - 'engine.inspector.pageBlock.option.location.list_item': 'List item', - // `…option.location.global_nav` retired with the location itself - // (objectstack#6888, @objectstack/spec 17.0.0-rc.6). Its only reader was the - // dropdown option removed from `previews/block-config.ts`; a key kept past its - // option is dead vocabulary that the next author reads as a live surface. // Report default ("home") inspector 'engine.inspector.report.kind': 'Report', 'engine.inspector.report.close': 'Close report', @@ -1050,9 +675,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.inspector.report.noSchema': 'Spec schema unavailable — basic properties only.', // Trailing section for fields the live server has but the bundled spec lacks. 'engine.inspector.moreFields': 'More fields', - // Action default (home) inspector - 'engine.inspector.action.kind': 'Action', - 'engine.inspector.action.close': 'Close action', 'engine.inspector.hook.kind': 'Hook', // Hook default (home) inspector (objectui#10586). Event names, code samples // and the priority digits stay literal: they are identifiers, not words. @@ -1085,135 +707,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.inspector.hook.async': 'Run asynchronously (after commit)', 'engine.inspector.hook.condition': 'Run only when (optional CEL)', 'engine.inspector.hook.moreFieldsHint': 'Advanced / rarely-used properties.', - // Action default (home) inspector (objectui#10586). Option labels are keyed - // by the stored value; `{…}` tokens are slots the inspector fills with a - // node (a code span, a capability list). HTTP methods, `↑` / `↓` and the - // path / dotted-key samples stay literal. - 'engine.inspector.action.type.script': 'Script — run an expression / sandboxed JS', - 'engine.inspector.action.type.api': 'API — call an endpoint', - 'engine.inspector.action.type.flow': 'Flow — invoke a flow', - 'engine.inspector.action.type.modal': 'Modal — open a modal/page', - 'engine.inspector.action.type.form': 'Form — open a FormView', - 'engine.inspector.action.type.url': 'URL — navigate to a link', - 'engine.inspector.action.variant.primary': 'Primary', - 'engine.inspector.action.variant.secondary': 'Secondary', - 'engine.inspector.action.variant.danger': 'Danger', - 'engine.inspector.action.variant.ghost': 'Ghost', - 'engine.inspector.action.variant.link': 'Link', - 'engine.inspector.action.component.button': 'Button', - 'engine.inspector.action.component.icon': 'Icon only', - 'engine.inspector.action.component.menu': 'Menu item', - 'engine.inspector.action.component.group': 'Button group', - 'engine.inspector.action.mode.create': 'Create', - 'engine.inspector.action.mode.edit': 'Edit', - 'engine.inspector.action.mode.delete': 'Delete', - 'engine.inspector.action.mode.custom': 'Custom', - 'engine.inspector.action.bodyLang.expression': 'Expression (L1)', - 'engine.inspector.action.bodyLang.js': 'Sandboxed JS (L2)', - 'engine.inspector.action.paramType.text': 'Text', - 'engine.inspector.action.paramType.textarea': 'Long text', - 'engine.inspector.action.paramType.number': 'Number', - 'engine.inspector.action.paramType.boolean': 'Checkbox', - 'engine.inspector.action.paramType.select': 'Select', - 'engine.inspector.action.paramType.date': 'Date', - 'engine.inspector.action.paramType.datetime': 'Date/time', - 'engine.inspector.action.paramType.lookup': 'Lookup', - 'engine.inspector.action.location.record_header': 'Record header', - 'engine.inspector.action.location.record_more': 'Record · more menu', - 'engine.inspector.action.location.record_section': 'Record · section', - 'engine.inspector.action.location.record_related': 'Record · related list', - 'engine.inspector.action.location.list_toolbar': 'List toolbar', - 'engine.inspector.action.location.list_item': 'List · row', - 'engine.inspector.action.target.url.label': 'URL', - 'engine.inspector.action.target.url.placeholder': 'https://… or /path?x=${param.x}', - 'engine.inspector.action.target.url.hint': 'Supports ${param.x} and ${ctx.x} interpolation.', - 'engine.inspector.action.target.flow.label': 'Flow name', - 'engine.inspector.action.target.flow.placeholder': 'snake_case flow', - 'engine.inspector.action.target.flow.hint': 'The flow to invoke when clicked.', - 'engine.inspector.action.target.modal.label': 'Modal / page name', - 'engine.inspector.action.target.modal.placeholder': 'snake_case page', - 'engine.inspector.action.target.modal.hint': 'The modal or page to open.', - 'engine.inspector.action.target.form.label': 'Form view name', - 'engine.inspector.action.target.form.hint': 'Opens /console/forms/.', - 'engine.inspector.action.target.api.label': 'API endpoint', - 'engine.inspector.action.target.api.hint': 'Endpoint called with the request body below.', - 'engine.inspector.action.scriptOp.run': 'Run a script — sandboxed JS / expression body', - 'engine.inspector.action.scriptOp.update': 'Update fields on this record — no code', - 'engine.inspector.action.objectPlaceholder': 'snake_case object', - 'engine.inspector.action.objectNone': '— None (global) —', - 'engine.inspector.action.options': 'Options', - 'engine.inspector.action.optionsEmpty': 'No choices yet — a Select with no options opens an empty picker in the dialog.', - 'engine.inspector.action.optionLabel': 'Label', - 'engine.inspector.action.optionValue': 'Value', - 'engine.inspector.action.removeOption': 'Remove option {index}', - 'engine.inspector.action.addOption': 'Add option', - 'engine.inspector.action.basics': 'Basics', - 'engine.inspector.action.label': 'Label', - 'engine.inspector.action.labelPlaceholder': 'Button text shown to users', - 'engine.inspector.action.name': 'Name', - 'engine.inspector.action.namePlaceholder': 'snake_case identifier', - 'engine.inspector.action.object': 'Object', - 'engine.inspector.action.objectBoundHint': 'Bound action — surfaces in this object’s views per the placement below.', - 'engine.inspector.action.objectGlobalHint': 'Empty = global action — must be referenced by a page’s quick actions, global nav, a flow, or AI to appear.', - 'engine.inspector.action.icon': 'Icon', - 'engine.inspector.action.variant': 'Variant', - 'engine.inspector.action.behavior': 'Behavior', - 'engine.inspector.action.behaviorHint': 'What happens when the action is triggered.', - 'engine.inspector.action.type': 'Type', - 'engine.inspector.action.pinnedScript': 'Pinned to {script}: the field write is performed on the platform action route, which is this type’s own route. Any other type is refused beside it.', - 'engine.inspector.action.whatItDoes': 'What it does', - 'engine.inspector.action.fieldValues': 'Field values', - 'engine.inspector.action.fieldValuesEmpty': 'No field values yet — add one, or collect the value as an input below.', - 'engine.inspector.action.fieldValueField': 'Field', - 'engine.inspector.action.fieldValueValue': 'Value', - 'engine.inspector.action.removeFieldValue': 'Remove field value', - 'engine.inspector.action.addFieldValue': 'Add field value', - 'engine.inspector.action.updateHint': 'Written to the current record as the caller — object permissions, hooks and validations fire as for a user edit. An input collected below with the same name overrides the fixed value here.', - 'engine.inspector.action.scriptLanguage': 'Script language', - 'engine.inspector.action.scriptBody': 'Script body', - 'engine.inspector.action.scriptBodyHint': 'Runs in the sandbox as {signature}.', - 'engine.inspector.action.method': 'Method', - 'engine.inspector.action.inputs': 'Inputs', - 'engine.inspector.action.inputsHint': 'Collected from the user in a dialog before the action runs.', - 'engine.inspector.action.inputsEmpty': 'No inputs — the action runs immediately on click.', - 'engine.inspector.action.removeInput': 'Remove input', - 'engine.inspector.action.bindToField': 'Bind to field', - 'engine.inspector.action.paramName': 'Name', - 'engine.inspector.action.paramNamePlaceholder': 'request-body key', - 'engine.inspector.action.paramLabel': 'Label', - 'engine.inspector.action.paramTypeLabel': 'Type', - 'engine.inspector.action.paramPlaceholder': 'Placeholder', - 'engine.inspector.action.paramRequired': 'Required', - 'engine.inspector.action.paramPrefill': 'Pre-fill from row', - 'engine.inspector.action.addInput': 'Add input', - 'engine.inspector.action.placement': 'Placement', - 'engine.inspector.action.placementHint': 'Where this action surfaces in the UI.', - 'engine.inspector.action.noPlacement': 'No placement selected — this action will not appear on any record or list surface. Tick a placement above, or place it from a view’s bulk actions.', - 'engine.inspector.action.capabilityGate': 'Capability-gated on {capabilities} — every placement ticked above hides this action from anyone who does not hold {whom}. Not greyed out and not an error: the button is simply absent, and the server refuses the invocation with a 403 either way.', - 'engine.inspector.action.capabilityGateAll': 'all of them', - 'engine.inspector.action.capabilityGateOne': 'it', - 'engine.inspector.action.capabilityGateSelf': 'This session does not hold {capabilities}, so the button is hidden from you too — grant it through a permission set to see it in the app.', - 'engine.inspector.action.component': 'Component', - 'engine.inspector.action.feedback': 'Feedback', - 'engine.inspector.action.feedbackHint': 'Confirmation and post-run messaging.', - 'engine.inspector.action.confirmText': 'Confirm prompt', - 'engine.inspector.action.confirmTextPlaceholder': 'Ask before running (leave blank to skip)', - 'engine.inspector.action.successMessage': 'Success message', - 'engine.inspector.action.errorMessage': 'Error message', - 'engine.inspector.action.mode': 'Mode', - 'engine.inspector.action.refreshAfter': 'Refresh view after', - 'engine.inspector.action.undoable': 'Offer undo', - 'engine.inspector.action.conditions': 'Conditions', - 'engine.inspector.action.conditionsHint': 'No-code predicates over the record / user / ctx (compiled to CEL).', - 'engine.inspector.action.visibleWhen': 'Visible when', - 'engine.inspector.action.disabledWhen': 'Disabled when', - 'engine.inspector.action.ai': 'AI exposure', - 'engine.inspector.action.aiHint': 'Opt-in: expose this action to AI agents as a callable tool.', - 'engine.inspector.action.aiExposed': 'Expose to AI agents', - 'engine.inspector.action.aiDescription': 'Tool description (required, ≥40 chars)', - 'engine.inspector.action.aiDescriptionPlaceholder': 'When and why an agent should call this action…', - 'engine.inspector.action.aiDescriptionTooShort': 'A ≥40-character description is required while exposed.', - 'engine.inspector.action.moreFieldsHint': 'Advanced / rarely-used properties.', // Dashboard default (home) inspector 'engine.inspector.dashboard.kind': 'Dashboard', 'engine.inspector.dashboard.close': 'Close dashboard', @@ -1225,97 +718,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.inspector.dashboard.widgetsEmpty': 'No widgets yet. Add one below.', 'engine.inspector.dashboard.removeWidget': 'Remove widget', 'engine.inspector.dashboard.noSchema': 'Spec schema unavailable — basic properties only.', - // Dataset default (home) inspector (objectui#10586). Option labels are keyed - // by the stored value; `{…}` tokens in a sentence are slots the inspector - // fills with a node (a code span, a count). - 'engine.inspector.dataset.kind': 'Dataset', - 'engine.inspector.dataset.aggregate.count': 'count', - 'engine.inspector.dataset.aggregate.sum': 'sum', - 'engine.inspector.dataset.aggregate.avg': 'avg', - 'engine.inspector.dataset.aggregate.min': 'min', - 'engine.inspector.dataset.aggregate.max': 'max', - 'engine.inspector.dataset.aggregate.count_distinct': 'count distinct', - 'engine.inspector.dataset.dimType.string': 'string', - 'engine.inspector.dataset.dimType.number': 'number', - 'engine.inspector.dataset.dimType.date': 'date', - 'engine.inspector.dataset.dimType.boolean': 'boolean', - 'engine.inspector.dataset.dimType.lookup': 'lookup', - 'engine.inspector.dataset.granularity.none': '— none —', - 'engine.inspector.dataset.granularity.day': 'day', - 'engine.inspector.dataset.granularity.week': 'week', - 'engine.inspector.dataset.granularity.month': 'month', - 'engine.inspector.dataset.granularity.quarter': 'quarter', - 'engine.inspector.dataset.granularity.year': 'year', - 'engine.inspector.dataset.derivedOp.ratio': 'ratio (a ÷ b)', - 'engine.inspector.dataset.derivedOp.sum': 'sum (a + b)', - 'engine.inspector.dataset.derivedOp.difference': 'difference (a − b)', - 'engine.inspector.dataset.derivedOp.product': 'product (a × b)', - 'engine.inspector.dataset.formatKind.raw': 'Raw number', - 'engine.inspector.dataset.formatKind.number': 'Number — 1,234.5', - 'engine.inspector.dataset.formatKind.currency': 'Currency — $1,234.50', - 'engine.inspector.dataset.formatKind.percent': 'Percent — 12.3%', - 'engine.inspector.dataset.usage.bound': 'Bound by {reports} · {dashboards} — changes affect them.', - 'engine.inspector.dataset.usage.reportsOne': '{count} report', - 'engine.inspector.dataset.usage.reportsOther': '{count} reports', - 'engine.inspector.dataset.usage.dashboardsOne': '{count} dashboard', - 'engine.inspector.dataset.usage.dashboardsOther': '{count} dashboards', - 'engine.inspector.dataset.usage.unbound': 'Not yet bound by any report or dashboard.', - 'engine.inspector.dataset.name': 'Name', - 'engine.inspector.dataset.namePlaceholder': 'snake_case identifier', - 'engine.inspector.dataset.label': 'Label', - 'engine.inspector.dataset.description': 'Description', - 'engine.inspector.dataset.baseObject': 'Base object', - 'engine.inspector.dataset.baseObjectPlaceholder': 'Select an object…', - 'engine.inspector.dataset.searchObjects': 'Search objects…', - 'engine.inspector.dataset.baseObjectChangeHint': 'Changing the base object clears its dimensions, field measures, joins & filters; a row count is kept.', - 'engine.inspector.dataset.includedRelationships': 'Included relationships', - 'engine.inspector.dataset.noJoins': 'No joins. Add a relationship (a lookup field on {object}) to use {path} dimensions/measures.', - 'engine.inspector.dataset.baseObjectFallback': 'the base object', - 'engine.inspector.dataset.includedRelationship': 'Included relationship', - 'engine.inspector.dataset.relationshipPlaceholder': 'Select a relationship…', - 'engine.inspector.dataset.searchRelationships': 'Search relationships…', - 'engine.inspector.dataset.removeRelationship': 'Remove relationship', - 'engine.inspector.dataset.scopeFilter': 'Scope filter', - 'engine.inspector.dataset.scopeFilterHelp': 'Intrinsic scope, ANDed into every query (e.g. exclude soft-deleted records).', - 'engine.inspector.dataset.dimensions': 'Dimensions', - 'engine.inspector.dataset.addDimension': 'Add dimension', - 'engine.inspector.dataset.dimensionN': 'Dimension {index}', - 'engine.inspector.dataset.removeDimension': 'Remove dimension', - 'engine.inspector.dataset.dimensionNamePlaceholder': 'e.g. region', - 'engine.inspector.dataset.field': 'Field', - 'engine.inspector.dataset.dimensionFieldPlaceholder': 'field or relationship.field', - 'engine.inspector.dataset.type': 'Type', - 'engine.inspector.dataset.labelOptional': 'Label (optional)', - 'engine.inspector.dataset.displayLabel': 'Display label', - 'engine.inspector.dataset.dateBucket': 'Date bucket', - 'engine.inspector.dataset.measures': 'Measures', - 'engine.inspector.dataset.addMeasure': 'Add measure', - 'engine.inspector.dataset.measureN': 'Measure {index}', - 'engine.inspector.dataset.removeMeasure': 'Remove measure', - 'engine.inspector.dataset.measureNamePlaceholder': 'e.g. revenue', - 'engine.inspector.dataset.aggregate': 'Aggregate', - 'engine.inspector.dataset.measureFieldPlaceholder': 'field (optional for count)', - 'engine.inspector.dataset.measureFilter': 'Filter (measure-scoped)', - 'engine.inspector.dataset.measureFilterHelp': 'Only rows matching this filter feed this measure (e.g. won_amount = sum(amount) where stage = won).', - 'engine.inspector.dataset.derived': 'Derived — computed from other measures', - 'engine.inspector.dataset.operation': 'Operation', - 'engine.inspector.dataset.operands': 'Operands (other measures)', - 'engine.inspector.dataset.derivedNeedTwo': 'Select exactly 2 measures for {op}.', - 'engine.inspector.dataset.derivedNeedOne': 'Select at least 1 measure for {op}.', - 'engine.inspector.dataset.addOtherMeasuresFirst': 'Add other measures first.', - 'engine.inspector.dataset.advanced': 'Advanced', - 'engine.inspector.dataset.displayFormat': 'Display format', - 'engine.inspector.dataset.decimals': 'Decimals', - 'engine.inspector.dataset.currency': 'Currency', - 'engine.inspector.dataset.sample': 'Sample: {sample}', - 'engine.inspector.dataset.relWarning': "Relationship {rel} isn't in Included relationships.", - 'engine.inspector.dataset.relWarningAdd': 'Add it', - 'engine.inspector.dataset.filterAdvanced': 'Advanced filter (nested / OR) — edit it in the {source} tab.', - 'engine.inspector.dataset.sourceTab': 'Source', - 'engine.inspector.dataset.filterConditionsOne': '{count} condition', - 'engine.inspector.dataset.filterConditionsOther': '{count} conditions', - 'engine.inspector.dataset.addFilter': '+ Add filter…', - 'engine.inspector.dataset.filterNeedsObject': 'Pick a base object to add filter conditions.', // Flow reference picker — ReferenceCombobox / FlowReferenceField (objectui#10586) 'engine.inspector.reference.pickFromRecords': 'Pick from records', 'engine.inspector.reference.pickFromRegistry': 'Pick from declared items', @@ -3835,52 +3237,7 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.edit.exitFullscreen': '退出全屏', 'engine.edit.designMode': '设计', 'engine.edit.previewMode': '预览', - 'engine.inspector.widget.kind': '组件', - 'engine.inspector.widget.close': '关闭组件', - 'engine.inspector.widget.title': '标题', - 'engine.inspector.widget.type': '类型', - 'engine.inspector.widget.object': '数据源(对象)', - 'engine.inspector.widget.valueField': '取值字段', - 'engine.inspector.widget.categoryField': '分组字段', - 'engine.inspector.widget.aggregate': '聚合方式', - 'engine.inspector.widget.color': '颜色', - 'engine.inspector.widget.width': '宽度', - 'engine.inspector.widget.height': '高度', - 'engine.inspector.widget.remove': '删除组件', 'engine.inspector.draftSuffix': '(草稿)', - // Dataset binding (ADR-0021) — governed cross-object semantic layer. - 'engine.inspector.widget.datasetSection': '数据集绑定', - 'engine.inspector.widget.dataset': '数据集', - 'engine.inspector.widget.datasetPlaceholder': '例如:sales_pipeline', - 'engine.inspector.widget.datasetHint': - '绑定一个受治理的数据集。设置后优先于上方的内联对象查询,组件改为按数据集渲染(口径一致、可跨对象、强制 RLS)。', - 'engine.inspector.widget.dimensions': '维度', - 'engine.inspector.widget.dimensionsPlaceholder': '例如:stage, region(逗号分隔)', - 'engine.inspector.widget.dimensionsHint': '用于分组的数据集维度名。留空则渲染为单值 KPI。', - 'engine.inspector.widget.values': '值(度量)', - 'engine.inspector.widget.valuesPlaceholder': '例如:revenue, deal_count(逗号分隔)', - 'engine.inspector.widget.valuesHint': '要展示的数据集度量名。', - // Dashboard filter bindings (framework#2501) - 'engine.inspector.widget.filterBindingsSection': '仪表盘过滤器绑定', - 'engine.inspector.widget.filterBindingsHint': - '把每个仪表盘级过滤器映射到本组件自己的字段;取消勾选「应用」可让本组件不受该过滤器影响。留空表示使用过滤器自身的字段。', - 'engine.inspector.widget.filterBindingApply': '应用', - 'engine.inspector.widget.filterBindingDefault': '默认({field})', - 'engine.inspector.widget.filterBindingField': '{filter} 绑定的字段', - 'engine.inspector.widget.filterBindingReset': '恢复默认', - 'engine.inspector.widget.type.metric': 'KPI 指标', - 'engine.inspector.widget.type.bar': '柱状图', - 'engine.inspector.widget.type.horizontalBar': '条形图', - 'engine.inspector.widget.type.line': '折线图', - 'engine.inspector.widget.type.area': '面积图', - 'engine.inspector.widget.type.pie': '饼图', - 'engine.inspector.widget.type.donut': '环形图', - 'engine.inspector.widget.type.funnel': '漏斗图', - 'engine.inspector.widget.type.table': '表格', - 'engine.inspector.widget.type.pivot': '透视表', - 'engine.inspector.widget.untitledN': '组件 {n}', - 'engine.inspector.widget.unsupportedSelection': '不支持的选择类型:{kind}', - 'engine.inspector.widget.removed': '所选组件已从草稿中移除。', 'engine.widgetPicker.search': '搜索组件…', 'engine.widgetPicker.noMatches': '没有匹配项。', 'engine.widgetPicker.category.kpi': '单值', @@ -3907,53 +3264,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.dashboardPreview.saveTitle': '保存标题', 'engine.dashboardPreview.renameWidget': '重命名组件', 'engine.dashboardPreview.clearSelection': '取消选择', - // Flow node inspector - 'engine.inspector.flowNode.kind': '节点', - 'engine.inspector.flowNode.close': '关闭节点', - 'engine.inspector.flowNode.id': 'ID', - 'engine.inspector.flowNode.idRequired': '未重命名:节点必须有 ID。', - 'engine.inspector.flowNode.idTaken': '未重命名:此流程中已有另一个节点的 ID 是“{id}”。', - 'engine.inspector.flowNode.idEdgeNamed': '未重命名:仍有连线指向“{id}”,而该节点已不在流程中。请先删除或重新连接那条连线。', - 'engine.inspector.flowNode.idRefsUnparsed': '未重命名:以下表达式引用了“{id}”,但无法解析,重命名无法同步更新它们:{refs}。请先修正。', - 'engine.inspector.flowNode.idRefsAmbiguous': '未重命名:“{name}”同时也是此流程中某个变量的名称,以下表达式可能引用其中任何一个,无法同步更新:{refs}。请先重命名该变量,或手动修改这些表达式。', - 'engine.inspector.flowNode.removeRefused': '未删除:以下位置仍引用“{id}”,删除后将指向不存在的节点:{refs}。请先修改或删除它们。', - 'engine.inspector.flowNode.label': '标签', - 'engine.inspector.flowNode.type': '节点类型', - 'engine.inspector.flowNode.configuration': '配置', - 'engine.inspector.flowNode.config': '配置(JSON)', - 'engine.inspector.flowNode.advanced': '高级(JSON)', - 'engine.inspector.flowNode.advancedHint': '上方表单未覆盖的可选自定义键 —— 大多数流程无需填写。', - 'engine.inspector.flowNode.inactiveRetained': '已保留,未生效 —— 控制字段处于关闭。该值仍然存储,直到你清除它。', - 'engine.inspector.flowNode.inactiveRetainedOrphan': '已保留,未生效 —— 没有任何开关能启用此字段。该值仍然存储,以免静默丢失。', - 'engine.inspector.flowNode.inactiveRetainedClear': '清除值', - 'engine.inspector.flowNode.cadenceLabel': '调度', - 'engine.inspector.flowNode.cadenceEveryMs': '每 {n} 毫秒', - 'engine.inspector.flowNode.cadenceEverySeconds': '每 {n} 秒', - 'engine.inspector.flowNode.cadenceEveryMinutes': '每 {n} 分钟', - 'engine.inspector.flowNode.cadenceEveryHours': '每 {n} 小时', - 'engine.inspector.flowNode.cadenceEveryDays': '每 {n} 天', - 'engine.inspector.flowNode.cadenceOnce': '仅一次,于 {at}', - 'engine.inspector.flowNode.cadenceReadOnly': '此流程按固定间隔或在指定时刻运行,而非按 cron。设计器只编辑 cron 调度,因此这里按存储原样显示此调度,且保持不变。', - 'engine.inspector.flowNode.noConfig': '此节点类型无需配置。', - 'engine.inspector.flowNode.nestedIdHint': '容器区域内的节点 ID 在此只读 —— 请在容器的高级 JSON 中重命名。', - 'engine.inspector.flowNode.nestedLocation': '嵌套节点位置', - 'engine.inspector.flowNode.advancedNotObject': '必须是 JSON 对象', - 'engine.inspector.flowNode.kv.add': '添加条目', - 'engine.inspector.flowNode.kv.key': '键', - 'engine.inspector.flowNode.kv.value': '值', - 'engine.inspector.flowNode.kv.remove': '删除条目', - 'engine.inspector.flowNode.kv.empty': '暂无条目。', - 'engine.inspector.flowNode.kv.asExpression': '写成 CEL 表达式', - 'engine.inspector.flowNode.kv.expressionPlaceholder': 'CEL 表达式,例如 joinNonEmpty(…)', - 'engine.inspector.flowNode.list.add': '添加项', - 'engine.inspector.flowNode.list.item': '值', - 'engine.inspector.flowNode.list.remove': '删除项', - 'engine.inspector.flowNode.list.empty': '暂无项。', - 'engine.inspector.flowNode.branch.add': '添加分支', - 'engine.inspector.flowNode.branch.fromEdges': '此决策按其连线上的条件路由。每张卡片是一条连线,请在连线上编辑它的条件。', - 'engine.inspector.flowNode.branch.openEdge': '编辑连线', - 'engine.inspector.flowNode.branch.noCondition': '无条件', - 'engine.inspector.flowNode.remove': '删除节点', // Flow edge (connection) inspector 'engine.inspector.flowEdge.kind': '连线', 'engine.inspector.flowEdge.close': '关闭连线', @@ -3989,56 +3299,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.inspector.workflowAction.name': '名称', 'engine.inspector.workflowAction.config': '配置(JSON)', 'engine.inspector.workflowAction.remove': '删除动作', - // App nav inspector - 'engine.inspector.appNav.kind': '导航项', - 'engine.inspector.appNav.close': '关闭导航项', - 'engine.inspector.appNav.label': '名称', - 'engine.inspector.appNav.icon': '图标', - 'engine.inspector.appNav.remove': '删除导航项', - 'engine.inspector.appNav.typeField': '类型', - 'engine.inspector.appNav.type.object': '对象', - 'engine.inspector.appNav.type.page': '页面', - 'engine.inspector.appNav.type.dashboard': '仪表盘', - 'engine.inspector.appNav.type.report': '报表', - 'engine.inspector.appNav.type.url': '链接(URL)', - 'engine.inspector.appNav.type.group': '分组', - 'engine.inspector.appNav.object': '对象', - 'engine.inspector.appNav.targetMode': '落点', - 'engine.inspector.appNav.mode.default': '默认视图(工作台)', - 'engine.inspector.appNav.mode.view': '具名视图', - 'engine.inspector.appNav.mode.record': '记录深链', - 'engine.inspector.appNav.mode.filters': '参数化切面(/data)', - 'engine.inspector.appNav.view': '视图', - 'engine.inspector.appNav.recordId': '记录 ID', - 'engine.inspector.appNav.recordIdHint': '支持 {current_user_id} 与 {current_org_id} 模板变量。', - 'engine.inspector.appNav.recordMode': '打开方式', - 'engine.inspector.appNav.recordModeView': '查看', - 'engine.inspector.appNav.recordModeEdit': '编辑', - 'engine.inspector.appNav.filters': 'URL 过滤条件', - 'engine.inspector.appNav.filtersField': '字段', - 'engine.inspector.appNav.filtersValue': '值', - 'engine.inspector.appNav.filtersAdd': '添加条件', - 'engine.inspector.appNav.filtersRemove': '移除条件', - 'engine.inspector.appNav.filtersHint': '一次性切面由 URL 承载——当它值得沉淀复用时,请改建具名视图。', - 'engine.inspector.appNav.url': '链接地址', - 'engine.inspector.appNav.urlTarget': '打开位置', - 'engine.inspector.appNav.urlTargetSelf': '当前标签页', - 'engine.inspector.appNav.urlTargetBlank': '新标签页', - 'engine.inspector.appNav.preview': '运行期落点', - // objectui#11790 — the Studio nav editor's Type choice and pickers. - 'engine.inspector.appNav.type.action': '动作', - 'engine.inspector.appNav.type.component': '组件', - 'engine.inspector.appNav.type.doc': '文档', - 'engine.inspector.appNav.type.separator': '分隔线', - 'engine.inspector.appNav.choose': '— 请选择 —', - 'engine.inspector.appNav.noTargets': '还没有可链接的此类型条目。', - 'engine.inspector.appNav.unboundHint': '选定目标之前,该导航项保留在此处,但不会写入保存的导航。', - 'engine.inspector.appNav.actionHint': '只有未绑定到对象的动作才能从菜单运行。', - 'engine.inspector.appNav.docPage': '文档页', - 'engine.inspector.appNav.book': '文档集', - 'engine.inspector.appNav.docHint': '选择文档页、文档集,或两者都选。', - 'engine.inspector.appNav.groupHint': '分组是菜单中带标题的分区。此编辑器不支持把导航项嵌套到分组下。', - 'engine.inspector.appNav.separatorHint': '分隔线只是一条分隔:没有名称,也不打开任何内容。', // View column inspector 'engine.inspector.viewColumn.kind': '列', 'engine.inspector.viewColumn.close': '关闭列', @@ -4115,191 +3375,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.inspector.view.type.timeline': '时间线', 'engine.inspector.view.type.map': '地图', 'engine.inspector.view.type.chart': '图表', - // Page block inspector - 'engine.inspector.pageBlock.kind': '区块', - 'engine.inspector.pageBlock.close': '关闭区块', - 'engine.inspector.pageBlock.type': '类型', - 'engine.inspector.pageBlock.id': 'ID', - 'engine.inspector.pageBlock.className': '类名', - 'engine.inspector.pageBlock.visibleWhen': '显示条件(CEL)', - 'engine.inspector.pageBlock.properties': '属性', - 'engine.inspector.pageBlock.advanced': '高级属性', - 'engine.inspector.pageBlock.remove': '删除区块', - 'engine.inspector.pageBlock.outlineLabel': '区块', - // 面板自身列表/JSON 编辑器的 chrome(#3963)—— 不是 block-config 表数据。 - 'engine.inspector.pageBlock.list.add': '添加', - 'engine.inspector.pageBlock.list.remove': '删除', - 'engine.inspector.pageBlock.list.removeItem': '删除项', - 'engine.inspector.pageBlock.objectPlaceholder': 'snake_case 对象名', - 'engine.inspector.pageBlock.fieldPlaceholder': '字段名', - // 页面区块检查器 —— curated 属性 label(#3913)。 - // 键形状与 en 侧一一对应,由 `previews/__tests__/block-config-i18n.test.ts` - // 按 BLOCK_CONFIG 的结构重新推导并断言两语齐备。 - 'engine.inspector.pageBlock.field.object-grid.objectName': '对象', - 'engine.inspector.pageBlock.field.object-grid.columns': '列', - 'engine.inspector.pageBlock.field.object-grid.pageSize': '每页条数', - 'engine.inspector.pageBlock.field.object-form.objectName': '对象', - 'engine.inspector.pageBlock.field.object-form.mode': '模式', - 'engine.inspector.pageBlock.option.mode.create': '新建', - 'engine.inspector.pageBlock.option.mode.edit': '编辑', - 'engine.inspector.pageBlock.option.mode.view': '查看', - 'engine.inspector.pageBlock.field.object-form.formType': '表单类型', - 'engine.inspector.pageBlock.option.formType.simple': '简单', - 'engine.inspector.pageBlock.option.formType.tabbed': '标签页', - 'engine.inspector.pageBlock.option.formType.wizard': '向导', - 'engine.inspector.pageBlock.option.formType.split': '分栏', - 'engine.inspector.pageBlock.option.formType.drawer': '抽屉', - 'engine.inspector.pageBlock.option.formType.modal': '弹窗', - 'engine.inspector.pageBlock.field.object-form.layout': '布局', - 'engine.inspector.pageBlock.option.layout.vertical': '纵向', - 'engine.inspector.pageBlock.option.layout.horizontal': '横向', - 'engine.inspector.pageBlock.field.object-form.columns': '字段列数(1–4)', - 'engine.inspector.pageBlock.field.object-form.fields': '字段', - 'engine.inspector.pageBlock.field.object-form.title': '标题', - 'engine.inspector.pageBlock.field.object-form.description': '描述', - 'engine.inspector.pageBlock.field.object-metric.objectName': '对象', - 'engine.inspector.pageBlock.field.object-metric.label': '标签', - 'engine.inspector.pageBlock.field.object-metric.description': '描述', - 'engine.inspector.pageBlock.field.object-metric.icon': '图标', - 'engine.inspector.pageBlock.placeholder.object-metric.icon': 'lucide 图标名', - 'engine.inspector.pageBlock.field.object-metric.colorVariant': '颜色', - 'engine.inspector.pageBlock.option.colorVariant.default': '默认', - 'engine.inspector.pageBlock.option.colorVariant.blue': '蓝色', - 'engine.inspector.pageBlock.option.colorVariant.teal': '青色', - 'engine.inspector.pageBlock.option.colorVariant.orange': '橙色', - 'engine.inspector.pageBlock.option.colorVariant.purple': '紫色', - 'engine.inspector.pageBlock.option.colorVariant.success': '成功', - 'engine.inspector.pageBlock.option.colorVariant.warning': '警告', - 'engine.inspector.pageBlock.option.colorVariant.danger': '危险', - 'engine.inspector.pageBlock.field.object-metric.format': '格式', - 'engine.inspector.pageBlock.option.format.number': '数字', - 'engine.inspector.pageBlock.option.format.currency': '货币', - 'engine.inspector.pageBlock.option.format.percent': '百分比', - 'engine.inspector.pageBlock.field.object-metric.prefix': '前缀', - 'engine.inspector.pageBlock.field.object-metric.suffix': '后缀', - 'engine.inspector.pageBlock.field.object-kanban.objectName': '对象', - 'engine.inspector.pageBlock.field.object-kanban.groupBy': '分组字段', - 'engine.inspector.pageBlock.field.object-kanban.titleField': '标题字段', - 'engine.inspector.pageBlock.field.object-kanban.cardFields': '卡片字段', - 'engine.inspector.pageBlock.field.object-kanban.limit': '条数上限', - 'engine.inspector.pageBlock.field.grid.columns': '列数', - 'engine.inspector.pageBlock.field.grid.gap': '间距', - 'engine.inspector.pageBlock.field.element:text.content': '内容', - 'engine.inspector.pageBlock.placeholder.element:text.content': '文本…', - 'engine.inspector.pageBlock.field.element:text.variant': '样式', - 'engine.inspector.pageBlock.option.variant.h1': '标题 1', - 'engine.inspector.pageBlock.option.variant.h2': '标题 2', - 'engine.inspector.pageBlock.option.variant.h3': '标题 3', - 'engine.inspector.pageBlock.option.variant.h4': '标题 4', - 'engine.inspector.pageBlock.option.variant.h5': '标题 5', - 'engine.inspector.pageBlock.option.variant.h6': '标题 6', - 'engine.inspector.pageBlock.option.variant.body': '正文', - 'engine.inspector.pageBlock.option.variant.caption': '说明文字', - 'engine.inspector.pageBlock.option.variant.overline': '眉题', - 'engine.inspector.pageBlock.field.element:text.align': '对齐', - 'engine.inspector.pageBlock.option.align.left': '左对齐', - 'engine.inspector.pageBlock.option.align.center': '居中', - 'engine.inspector.pageBlock.option.align.right': '右对齐', - 'engine.inspector.pageBlock.field.element:image.src': '图片地址', - 'engine.inspector.pageBlock.field.element:image.alt': '替代文本', - 'engine.inspector.pageBlock.field.element:image.fit': '填充方式', - 'engine.inspector.pageBlock.option.fit.cover': '覆盖', - 'engine.inspector.pageBlock.option.fit.contain': '包含', - 'engine.inspector.pageBlock.option.fit.fill': '拉伸', - 'engine.inspector.pageBlock.field.element:definition-list.items': '条目', - 'engine.inspector.pageBlock.add.element:definition-list.items': '添加条目', - 'engine.inspector.pageBlock.field.element:definition-list.items.term': '术语', - 'engine.inspector.pageBlock.field.element:definition-list.items.description': '描述', - 'engine.inspector.pageBlock.field.element:definition-list.columns': '列数(1 或 2)', - 'engine.inspector.pageBlock.field.element:definition-list.inline': '行内(术语 · 描述)', - 'engine.inspector.pageBlock.field.element:repeater.object': '对象', - 'engine.inspector.pageBlock.field.element:repeater.titleField': '标题字段', - 'engine.inspector.pageBlock.field.element:repeater.fields': '字段', - 'engine.inspector.pageBlock.field.element:repeater.limit': '条数上限', - 'engine.inspector.pageBlock.field.element:repeater.emptyText': '空数据提示', - 'engine.inspector.pageBlock.field.element:repeater.divided': '行间分隔线', - 'engine.inspector.pageBlock.field.element:number.object': '对象', - 'engine.inspector.pageBlock.field.element:number.field': '字段', - 'engine.inspector.pageBlock.field.element:number.aggregate': '聚合方式', - 'engine.inspector.pageBlock.option.aggregate.count': '计数', - 'engine.inspector.pageBlock.option.aggregate.sum': '求和', - 'engine.inspector.pageBlock.option.aggregate.avg': '平均值', - 'engine.inspector.pageBlock.option.aggregate.min': '最小值', - 'engine.inspector.pageBlock.option.aggregate.max': '最大值', - 'engine.inspector.pageBlock.field.element:number.format': '格式', - 'engine.inspector.pageBlock.field.element:number.prefix': '前缀', - 'engine.inspector.pageBlock.field.element:number.suffix': '后缀', - 'engine.inspector.pageBlock.field.element:button.label': '按钮文字', - 'engine.inspector.pageBlock.field.element:button.variant': '样式', - 'engine.inspector.pageBlock.option.variant.primary': '主要', - 'engine.inspector.pageBlock.option.variant.secondary': '次要', - 'engine.inspector.pageBlock.option.variant.danger': '危险', - 'engine.inspector.pageBlock.option.variant.ghost': '幽灵', - 'engine.inspector.pageBlock.option.variant.link': '链接', - 'engine.inspector.pageBlock.field.element:button.size': '尺寸', - 'engine.inspector.pageBlock.option.size.small': '小', - 'engine.inspector.pageBlock.option.size.medium': '中', - 'engine.inspector.pageBlock.option.size.large': '大', - 'engine.inspector.pageBlock.field.element:button.icon': '图标', - 'engine.inspector.pageBlock.placeholder.element:button.icon': 'lucide 图标名', - 'engine.inspector.pageBlock.field.element:button.action': '动作', - 'engine.inspector.pageBlock.field.page:header.title': '标题', - 'engine.inspector.pageBlock.field.page:header.subtitle': '副标题', - // `…field.page:header.icon` and its placeholder retired with the spec key — - // see the matching note in the `en` table above. Removed from BOTH tables in - // the same edit so the two key sets stay identical. `…field.page:header.breadcrumb` - // likewise left both tables with its toggle (objectui#11173). - 'engine.inspector.pageBlock.field.page:card.title': '标题', - 'engine.inspector.pageBlock.field.page:card.bordered': '显示边框', - 'engine.inspector.pageBlock.field.page:tabs.items': '标签页', - 'engine.inspector.pageBlock.add.page:tabs.items': '添加标签页', - 'engine.inspector.pageBlock.field.page:tabs.items.value': '值', - 'engine.inspector.pageBlock.field.page:tabs.items.label': '标签', - // `…field.page:accordion.title` / `…items.value` retired with their designer - // fields — see the matching note in the `en` table above. Removed from BOTH - // tables in the same edit so the two key sets stay identical. - 'engine.inspector.pageBlock.field.page:accordion.items': '分区', - 'engine.inspector.pageBlock.add.page:accordion.items': '添加分区', - 'engine.inspector.pageBlock.field.page:accordion.items.label': '标签', - 'engine.inspector.pageBlock.field.record:related_list.objectName': '对象', - 'engine.inspector.pageBlock.field.record:related_list.relationshipField': '关联字段', - 'engine.inspector.pageBlock.field.record:related_list.title': '标题', - 'engine.inspector.pageBlock.field.record:related_list.limit': '条数上限', - 'engine.inspector.pageBlock.field.record:highlights.fields': '字段', - 'engine.inspector.pageBlock.field.record:details.sections': '分区', - 'engine.inspector.pageBlock.add.record:details.sections': '添加分区', - 'engine.inspector.pageBlock.field.record:details.sections.name': '名称(i18n 键)', - 'engine.inspector.pageBlock.placeholder.record:details.sections.name': 'snake_case,例如:contact_info', - 'engine.inspector.pageBlock.field.record:details.sections.label': '标签', - 'engine.inspector.pageBlock.field.record:details.sections.columns': '列数', - 'engine.inspector.pageBlock.field.record:details.sections.fields': '字段', - 'engine.inspector.pageBlock.field.record:alert.severity': '严重度', - 'engine.inspector.pageBlock.option.severity.info': '信息', - 'engine.inspector.pageBlock.option.severity.warning': '警告', - 'engine.inspector.pageBlock.option.severity.error': '错误', - 'engine.inspector.pageBlock.option.severity.success': '成功', - 'engine.inspector.pageBlock.field.record:alert.title': '标题', - 'engine.inspector.pageBlock.field.record:alert.body': '正文', - 'engine.inspector.pageBlock.field.record:alert.icon': '图标', - 'engine.inspector.pageBlock.placeholder.record:alert.icon': 'lucide 图标名', - 'engine.inspector.pageBlock.field.record:alert.dismissible': '可关闭', - 'engine.inspector.pageBlock.field.record:path.statusField': '状态字段', - 'engine.inspector.pageBlock.field.record:path.stages': '阶段', - 'engine.inspector.pageBlock.add.record:path.stages': '添加阶段', - 'engine.inspector.pageBlock.field.record:path.stages.value': '值', - 'engine.inspector.pageBlock.field.record:path.stages.label': '标签', - 'engine.inspector.pageBlock.field.record:quick_actions.actionNames': '动作名称', - 'engine.inspector.pageBlock.placeholder.record:quick_actions.actionNames': '动作名称', - 'engine.inspector.pageBlock.field.record:quick_actions.location': '位置', - 'engine.inspector.pageBlock.option.location.record_header': '记录头部', - 'engine.inspector.pageBlock.option.location.record_more': '记录更多菜单', - 'engine.inspector.pageBlock.option.location.record_section': '记录分区', - 'engine.inspector.pageBlock.option.location.record_related': '记录关联列表', - 'engine.inspector.pageBlock.option.location.list_toolbar': '列表工具栏', - 'engine.inspector.pageBlock.option.location.list_item': '列表行', - // `…option.location.global_nav` retired with the location itself — see the - // matching note in the `en` table above. Removed from BOTH tables in the same - // edit so the two key sets stay identical. // Report default ("home") inspector 'engine.inspector.report.kind': '报表', 'engine.inspector.report.close': '关闭报表', @@ -4330,9 +3405,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.inspector.report.noSchema': '规格 schema 不可用 —— 仅显示基础属性。', // Trailing section for fields the live server has but the bundled spec lacks. 'engine.inspector.moreFields': '更多字段', - // Action default (home) inspector - 'engine.inspector.action.kind': '动作', - 'engine.inspector.action.close': '关闭动作', 'engine.inspector.hook.kind': '钩子', // Hook default (home) inspector (objectui#10586). 'engine.inspector.hook.bodyLang.expression': '表达式(L1)', @@ -4364,134 +3436,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.inspector.hook.async': '异步运行(提交之后)', 'engine.inspector.hook.condition': '仅在以下条件成立时运行(可选 CEL)', 'engine.inspector.hook.moreFieldsHint': '高级 / 不常用的属性。', - // Action default (home) inspector (objectui#10586). Wording follows the - // catalogue's own rows: pageBlock's variant and location labels, the - // `engine.fieldType.*` names, `designer.field.*` (显示名 / 占位符 / 必填). - 'engine.inspector.action.type.script': '脚本 —— 运行表达式 / 沙箱 JS', - 'engine.inspector.action.type.api': 'API —— 调用端点', - 'engine.inspector.action.type.flow': '流程 —— 调用流程', - 'engine.inspector.action.type.modal': '弹窗 —— 打开弹窗 / 页面', - 'engine.inspector.action.type.form': '表单 —— 打开表单视图', - 'engine.inspector.action.type.url': 'URL —— 跳转到链接', - 'engine.inspector.action.variant.primary': '主要', - 'engine.inspector.action.variant.secondary': '次要', - 'engine.inspector.action.variant.danger': '危险', - 'engine.inspector.action.variant.ghost': '幽灵', - 'engine.inspector.action.variant.link': '链接', - 'engine.inspector.action.component.button': '按钮', - 'engine.inspector.action.component.icon': '仅图标', - 'engine.inspector.action.component.menu': '菜单项', - 'engine.inspector.action.component.group': '按钮组', - 'engine.inspector.action.mode.create': '新建', - 'engine.inspector.action.mode.edit': '编辑', - 'engine.inspector.action.mode.delete': '删除', - 'engine.inspector.action.mode.custom': '自定义', - 'engine.inspector.action.bodyLang.expression': '表达式(L1)', - 'engine.inspector.action.bodyLang.js': '沙箱 JS(L2)', - 'engine.inspector.action.paramType.text': '单行文本', - 'engine.inspector.action.paramType.textarea': '多行文本', - 'engine.inspector.action.paramType.number': '数字', - 'engine.inspector.action.paramType.boolean': '复选框', - 'engine.inspector.action.paramType.select': '下拉选择', - 'engine.inspector.action.paramType.date': '日期', - 'engine.inspector.action.paramType.datetime': '日期时间', - 'engine.inspector.action.paramType.lookup': '查找关系', - 'engine.inspector.action.location.record_header': '记录头部', - 'engine.inspector.action.location.record_more': '记录 · 更多菜单', - 'engine.inspector.action.location.record_section': '记录 · 分区', - 'engine.inspector.action.location.record_related': '记录 · 关联列表', - 'engine.inspector.action.location.list_toolbar': '列表工具栏', - 'engine.inspector.action.location.list_item': '列表 · 行', - 'engine.inspector.action.target.url.label': 'URL 地址', - 'engine.inspector.action.target.url.placeholder': 'https://… 或 /path?x=${param.x}', - 'engine.inspector.action.target.url.hint': '支持 ${param.x} 与 ${ctx.x} 插值。', - 'engine.inspector.action.target.flow.label': '流程名称', - 'engine.inspector.action.target.flow.placeholder': 'snake_case 流程名', - 'engine.inspector.action.target.flow.hint': '点击时调用的流程。', - 'engine.inspector.action.target.modal.label': '弹窗 / 页面名称', - 'engine.inspector.action.target.modal.placeholder': 'snake_case 页面名', - 'engine.inspector.action.target.modal.hint': '要打开的弹窗或页面。', - 'engine.inspector.action.target.form.label': '表单视图名称', - 'engine.inspector.action.target.form.hint': '打开 /console/forms/。', - 'engine.inspector.action.target.api.label': 'API 端点', - 'engine.inspector.action.target.api.hint': '使用下方请求体调用的端点。', - 'engine.inspector.action.scriptOp.run': '运行脚本 —— 沙箱 JS / 表达式主体', - 'engine.inspector.action.scriptOp.update': '更新此记录的字段 —— 无需代码', - 'engine.inspector.action.objectPlaceholder': 'snake_case 对象名', - 'engine.inspector.action.objectNone': '— 无(全局)—', - 'engine.inspector.action.options': '选项', - 'engine.inspector.action.optionsEmpty': '还没有选项 —— 没有选项的下拉选择会在对话框中打开一个空的选择器。', - 'engine.inspector.action.optionLabel': '显示名', - 'engine.inspector.action.optionValue': '值', - 'engine.inspector.action.removeOption': '移除选项 {index}', - 'engine.inspector.action.addOption': '添加选项', - 'engine.inspector.action.basics': '基本信息', - 'engine.inspector.action.label': '显示名', - 'engine.inspector.action.labelPlaceholder': '向用户显示的按钮文字', - 'engine.inspector.action.name': '名称', - 'engine.inspector.action.namePlaceholder': 'snake_case 标识符', - 'engine.inspector.action.object': '对象', - 'engine.inspector.action.objectBoundHint': '绑定动作 —— 按下方的放置位置出现在此对象的视图中。', - 'engine.inspector.action.objectGlobalHint': '留空 = 全局动作 —— 须被页面的快捷动作、全局导航、流程或 AI 引用后才会出现。', - 'engine.inspector.action.icon': '图标', - 'engine.inspector.action.variant': '样式', - 'engine.inspector.action.behavior': '行为', - 'engine.inspector.action.behaviorHint': '动作被触发时会发生什么。', - 'engine.inspector.action.type': '类型', - 'engine.inspector.action.pinnedScript': '已固定为 {script}:字段写入在平台动作路由上执行,而这正是该类型自己的路由。与它并存时,其他任何类型都会被拒绝。', - 'engine.inspector.action.whatItDoes': '作用', - 'engine.inspector.action.fieldValues': '字段值', - 'engine.inspector.action.fieldValuesEmpty': '还没有字段值 —— 添加一个,或在下方把该值作为输入项收集。', - 'engine.inspector.action.fieldValueField': '字段', - 'engine.inspector.action.fieldValueValue': '值', - 'engine.inspector.action.removeFieldValue': '移除字段值', - 'engine.inspector.action.addFieldValue': '添加字段值', - 'engine.inspector.action.updateHint': '以调用者身份写入当前记录 —— 对象权限、钩子与校验会像用户编辑时一样触发。下方收集的同名输入项会覆盖此处的固定值。', - 'engine.inspector.action.scriptLanguage': '脚本语言', - 'engine.inspector.action.scriptBody': '脚本主体', - 'engine.inspector.action.scriptBodyHint': '在沙箱中以 {signature} 的形式运行。', - 'engine.inspector.action.method': '请求方法', - 'engine.inspector.action.inputs': '输入项', - 'engine.inspector.action.inputsHint': '在动作运行前通过对话框向用户收集。', - 'engine.inspector.action.inputsEmpty': '没有输入项 —— 点击后立即运行动作。', - 'engine.inspector.action.removeInput': '移除输入项', - 'engine.inspector.action.bindToField': '绑定到字段', - 'engine.inspector.action.paramName': '名称', - 'engine.inspector.action.paramNamePlaceholder': '请求体键名', - 'engine.inspector.action.paramLabel': '显示名', - 'engine.inspector.action.paramTypeLabel': '类型', - 'engine.inspector.action.paramPlaceholder': '占位符', - 'engine.inspector.action.paramRequired': '必填', - 'engine.inspector.action.paramPrefill': '从行预填', - 'engine.inspector.action.addInput': '添加输入项', - 'engine.inspector.action.placement': '放置位置', - 'engine.inspector.action.placementHint': '此动作在界面中出现的位置。', - 'engine.inspector.action.noPlacement': '未选择放置位置 —— 此动作不会出现在任何记录或列表界面上。请在上方勾选一个放置位置,或通过视图的批量动作放置它。', - 'engine.inspector.action.capabilityGate': '受权限 {capabilities} 控制 —— 对于不具备{whom}的任何人,上方勾选的每个放置位置都会隐藏此动作。这不是置灰,也不是错误:按钮只是不出现,而且无论如何服务器都会以 403 拒绝调用。', - 'engine.inspector.action.capabilityGateAll': '全部这些权限', - 'engine.inspector.action.capabilityGateOne': '该权限', - 'engine.inspector.action.capabilityGateSelf': '当前会话不具备 {capabilities},因此该按钮对你同样隐藏 —— 通过权限集授予后,即可在应用中看到它。', - 'engine.inspector.action.component': '组件', - 'engine.inspector.action.feedback': '反馈', - 'engine.inspector.action.feedbackHint': '确认提示与运行后的消息。', - 'engine.inspector.action.confirmText': '确认提示', - 'engine.inspector.action.confirmTextPlaceholder': '运行前询问(留空则跳过)', - 'engine.inspector.action.successMessage': '成功消息', - 'engine.inspector.action.errorMessage': '错误消息', - 'engine.inspector.action.mode': '模式', - 'engine.inspector.action.refreshAfter': '完成后刷新视图', - 'engine.inspector.action.undoable': '提供撤销', - 'engine.inspector.action.conditions': '条件', - 'engine.inspector.action.conditionsHint': '基于 record / user / ctx 的无代码判断条件(编译为 CEL)。', - 'engine.inspector.action.visibleWhen': '可见条件', - 'engine.inspector.action.disabledWhen': '禁用条件', - 'engine.inspector.action.ai': 'AI 开放', - 'engine.inspector.action.aiHint': '可选:将此动作作为可调用工具开放给 AI 智能体。', - 'engine.inspector.action.aiExposed': '开放给 AI 智能体', - 'engine.inspector.action.aiDescription': '工具描述(必填,≥40 个字符)', - 'engine.inspector.action.aiDescriptionPlaceholder': '智能体应在何时、出于何种目的调用此动作…', - 'engine.inspector.action.aiDescriptionTooShort': '开放期间必须提供至少 40 个字符的描述。', - 'engine.inspector.action.moreFieldsHint': '高级 / 不常用的属性。', // Dashboard default (home) inspector 'engine.inspector.dashboard.kind': '仪表盘', 'engine.inspector.dashboard.close': '关闭仪表盘', @@ -4503,96 +3447,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.inspector.dashboard.widgetsEmpty': '还没有组件。在下方添加。', 'engine.inspector.dashboard.removeWidget': '删除组件', 'engine.inspector.dashboard.noSchema': '规格 schema 不可用 —— 仅显示基础属性。', - // Dataset default (home) inspector (objectui#10586). zh has one plural form, - // so each `…One` / `…Other` pair carries the same row. - 'engine.inspector.dataset.kind': '数据集', - 'engine.inspector.dataset.aggregate.count': '计数', - 'engine.inspector.dataset.aggregate.sum': '求和', - 'engine.inspector.dataset.aggregate.avg': '平均值', - 'engine.inspector.dataset.aggregate.min': '最小值', - 'engine.inspector.dataset.aggregate.max': '最大值', - 'engine.inspector.dataset.aggregate.count_distinct': '去重计数', - 'engine.inspector.dataset.dimType.string': '字符串', - 'engine.inspector.dataset.dimType.number': '数字', - 'engine.inspector.dataset.dimType.date': '日期', - 'engine.inspector.dataset.dimType.boolean': '布尔值', - 'engine.inspector.dataset.dimType.lookup': '查找关系', - 'engine.inspector.dataset.granularity.none': '— 无 —', - 'engine.inspector.dataset.granularity.day': '日', - 'engine.inspector.dataset.granularity.week': '周', - 'engine.inspector.dataset.granularity.month': '月', - 'engine.inspector.dataset.granularity.quarter': '季度', - 'engine.inspector.dataset.granularity.year': '年', - 'engine.inspector.dataset.derivedOp.ratio': '比率(a ÷ b)', - 'engine.inspector.dataset.derivedOp.sum': '求和(a + b)', - 'engine.inspector.dataset.derivedOp.difference': '差值(a − b)', - 'engine.inspector.dataset.derivedOp.product': '乘积(a × b)', - 'engine.inspector.dataset.formatKind.raw': '原始数值', - 'engine.inspector.dataset.formatKind.number': '数字 —— 1,234.5', - 'engine.inspector.dataset.formatKind.currency': '货币 —— $1,234.50', - 'engine.inspector.dataset.formatKind.percent': '百分比 —— 12.3%', - 'engine.inspector.dataset.usage.bound': '已被 {reports} · {dashboards} 绑定 —— 修改会影响它们。', - 'engine.inspector.dataset.usage.reportsOne': '{count} 个报表', - 'engine.inspector.dataset.usage.reportsOther': '{count} 个报表', - 'engine.inspector.dataset.usage.dashboardsOne': '{count} 个仪表盘', - 'engine.inspector.dataset.usage.dashboardsOther': '{count} 个仪表盘', - 'engine.inspector.dataset.usage.unbound': '尚未被任何报表或仪表盘绑定。', - 'engine.inspector.dataset.name': '名称', - 'engine.inspector.dataset.namePlaceholder': 'snake_case 标识符', - 'engine.inspector.dataset.label': '显示名', - 'engine.inspector.dataset.description': '描述', - 'engine.inspector.dataset.baseObject': '基础对象', - 'engine.inspector.dataset.baseObjectPlaceholder': '选择对象…', - 'engine.inspector.dataset.searchObjects': '搜索对象…', - 'engine.inspector.dataset.baseObjectChangeHint': '更改基础对象会清空其维度、字段度量、关联与筛选;行计数会保留。', - 'engine.inspector.dataset.includedRelationships': '包含的关联', - 'engine.inspector.dataset.noJoins': '暂无关联。添加一个关联({object} 上的查找字段),即可使用 {path} 形式的维度 / 度量。', - 'engine.inspector.dataset.baseObjectFallback': '基础对象', - 'engine.inspector.dataset.includedRelationship': '包含的关联', - 'engine.inspector.dataset.relationshipPlaceholder': '选择关联…', - 'engine.inspector.dataset.searchRelationships': '搜索关联…', - 'engine.inspector.dataset.removeRelationship': '移除关联', - 'engine.inspector.dataset.scopeFilter': '范围筛选', - 'engine.inspector.dataset.scopeFilterHelp': '固有范围,以 AND 并入每一次查询(例如排除已软删除的记录)。', - 'engine.inspector.dataset.dimensions': '维度', - 'engine.inspector.dataset.addDimension': '添加维度', - 'engine.inspector.dataset.dimensionN': '维度 {index}', - 'engine.inspector.dataset.removeDimension': '移除维度', - 'engine.inspector.dataset.dimensionNamePlaceholder': '例如:region', - 'engine.inspector.dataset.field': '字段', - 'engine.inspector.dataset.dimensionFieldPlaceholder': '字段 或 关联.字段', - 'engine.inspector.dataset.type': '类型', - 'engine.inspector.dataset.labelOptional': '显示名(可选)', - 'engine.inspector.dataset.displayLabel': '显示名', - 'engine.inspector.dataset.dateBucket': '日期分桶', - 'engine.inspector.dataset.measures': '度量', - 'engine.inspector.dataset.addMeasure': '添加度量', - 'engine.inspector.dataset.measureN': '度量 {index}', - 'engine.inspector.dataset.removeMeasure': '移除度量', - 'engine.inspector.dataset.measureNamePlaceholder': '例如:revenue', - 'engine.inspector.dataset.aggregate': '聚合方式', - 'engine.inspector.dataset.measureFieldPlaceholder': '字段(count 时可选)', - 'engine.inspector.dataset.measureFilter': '筛选(度量级)', - 'engine.inspector.dataset.measureFilterHelp': '只有满足此筛选的行才计入该度量(例如 won_amount = sum(amount) where stage = won)。', - 'engine.inspector.dataset.derived': '派生 —— 由其他度量计算', - 'engine.inspector.dataset.operation': '运算', - 'engine.inspector.dataset.operands': '操作数(其他度量)', - 'engine.inspector.dataset.derivedNeedTwo': '{op} 需要恰好选择 2 个度量。', - 'engine.inspector.dataset.derivedNeedOne': '{op} 至少需要选择 1 个度量。', - 'engine.inspector.dataset.addOtherMeasuresFirst': '请先添加其他度量。', - 'engine.inspector.dataset.advanced': '高级', - 'engine.inspector.dataset.displayFormat': '显示格式', - 'engine.inspector.dataset.decimals': '小数位数', - 'engine.inspector.dataset.currency': '货币', - 'engine.inspector.dataset.sample': '示例:{sample}', - 'engine.inspector.dataset.relWarning': '关联 {rel} 不在「包含的关联」中。', - 'engine.inspector.dataset.relWarningAdd': '添加它', - 'engine.inspector.dataset.filterAdvanced': '高级筛选(嵌套 / OR)—— 请在「{source}」标签页中编辑。', - 'engine.inspector.dataset.sourceTab': '源码', - 'engine.inspector.dataset.filterConditionsOne': '{count} 个条件', - 'engine.inspector.dataset.filterConditionsOther': '{count} 个条件', - 'engine.inspector.dataset.addFilter': '+ 添加筛选…', - 'engine.inspector.dataset.filterNeedsObject': '先选择基础对象,才能添加筛选条件。', // Flow reference picker — ReferenceCombobox / FlowReferenceField (objectui#10586) 'engine.inspector.reference.pickFromRecords': '从记录中选择', 'engine.inspector.reference.pickFromRegistry': '从已声明的项中选择', diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.test.tsx index 4e8b321c1ed..a1bf7b0f79f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.test.tsx @@ -31,6 +31,11 @@ vi.mock('../previews/useDatasetCatalog', () => ({ })); import { DashboardWidgetInspector } from './DashboardWidgetInspector'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); afterEach(cleanup); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx index 86854c664d7..45bee268d88 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx @@ -80,6 +80,11 @@ import { registerDefaultMetadataSchemas } from '../default-schemas'; import { registerBuiltinPreviews } from '../previews'; import { registerBuiltinInspectors } from '.'; import { DatasetDefaultInspector, objectChangePatch } from './DatasetDefaultInspector'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); const DATASET_JSON_SCHEMA = z.toJSONSchema(getMetadataTypeSchema('dataset') as z.ZodType, { io: 'input', diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.renameNode-11827.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.renameNode-11827.test.tsx index ef0891d525f..f32cf765bfb 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.renameNode-11827.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.renameNode-11827.test.tsx @@ -31,6 +31,11 @@ import { FlowNodeInspector } from './FlowNodeInspector'; import { encodeNestedNodeId } from './flow-nested-selection'; import { t, tFormat, type SupportedLocale } from '../i18n'; import type { MetadataSelection } from '../preview-registry'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); afterEach(cleanup); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.colorLabelling.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.colorLabelling.test.tsx index 11c8b48651a..75c35a1df98 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.colorLabelling.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.colorLabelling.test.tsx @@ -45,6 +45,11 @@ const state = vi.hoisted(() => ({ vi.mock('../useMetadata', () => ({ useMetadataClient: () => state.metadataClient })); import { PageBlockInspector } from './PageBlockInspector'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); afterEach(cleanup); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.i18n.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.i18n.test.tsx index abcf1f8dc85..5fb96d0cc62 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.i18n.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.i18n.test.tsx @@ -48,6 +48,11 @@ vi.mock('../useMetadata', () => ({ })); import { PageBlockInspector } from './PageBlockInspector'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); afterEach(cleanup); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.pageHeaderBreadcrumb-11173.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.pageHeaderBreadcrumb-11173.test.tsx index 6b1b7dc7b8b..56d296434d4 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.pageHeaderBreadcrumb-11173.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.pageHeaderBreadcrumb-11173.test.tsx @@ -41,6 +41,11 @@ vi.mock('../useMetadata', () => ({ })); import { PageBlockInspector } from './PageBlockInspector'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); afterEach(cleanup); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.visibleWhen.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.visibleWhen.test.tsx index e57a556fdd0..10b32103796 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.visibleWhen.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.visibleWhen.test.tsx @@ -34,6 +34,11 @@ import { SchemaRenderer, PredicateScopeProvider } from '@object-ui/react'; import { PageBlockInspector } from './PageBlockInspector'; import type { MetadataInspectorProps } from '../inspector-registry'; import type { SupportedLocale } from '../i18n'; +// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); afterEach(cleanup); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts b/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts new file mode 100644 index 00000000000..8332a89e2f2 --- /dev/null +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts @@ -0,0 +1,62 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * objectstack#25922 slice S2 — the page block, action, dataset, flow node, app + * navigation and dashboard widget inspector rows (`engine.inspector.{pageBlock, + * action,dataset,flowNode,appNav,widget}.*`) live in `@object-ui/i18n`'s + * `studio` namespace, and app-shell's `t()` still answers every one of them the + * way it did when they sat in its own table. + * + * What would break it: + * - a row copied back into the app-shell table with a different value — the + * table is asked first, so it would shadow the namespace silently; + * - the namespace fallback dropped from `lookupString` — every moved key + * would echo back as itself; + * - a family emptied out of the namespace, which every per-key assertion + * below would then pass over nothing. The floors are per FAMILY, by name, + * so one family draining cannot hide behind another one growing. + * + * The oracle is the namespace's own catalogues, read through + * `loadStudioCatalogue` (the module's public door), not a copy of their text. + */ +import { describe, expect, it } from 'vitest'; +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +import { t } from '../i18n'; + +const EN = await loadStudioCatalogue('en'); +// Resident before any case, so the zh lookups below read synchronously. +const ZH = await loadStudioCatalogue('zh'); + +/** Family → the fewest keys it may carry (measured at the move, rounded down). */ +const S2_FAMILY_FLOOR: Readonly> = Object.freeze({ + pageBlock: 160, + action: 120, + dataset: 80, + flowNode: 40, + appNav: 40, + widget: 40, +}); + +const familyKeys = (family: string): string[] => + Object.keys(EN).filter((key) => key.startsWith(`engine.inspector.${family}.`)); + +describe('S2 inspector rows answer from the studio namespace (objectstack#25922)', () => { + it.each(Object.entries(S2_FAMILY_FLOOR))('%s: the namespace carries the family, in en and zh', (family, floor) => { + const keys = familyKeys(family); + expect(keys.length).toBeGreaterThanOrEqual(floor); + expect(keys.filter((key) => !(key in ZH))).toEqual([]); + }); + + it.each(Object.keys(S2_FAMILY_FLOOR))('%s: t() answers every key with the namespace value', (family) => { + const wrong = familyKeys(family).flatMap((key) => { + const out: string[] = []; + if (t(key, 'en-US') !== EN[key]) out.push(`en-US ${key}: ${t(key, 'en-US')}`); + if (t(key, 'zh-CN') !== ZH[key]) out.push(`zh-CN ${key}: ${t(key, 'zh-CN')}`); + // A language the namespace has no catalogue for reads English, as it did + // from the app-shell table. + if (t(key, 'de-DE') !== EN[key]) out.push(`de-DE ${key}: ${t(key, 'de-DE')}`); + return out; + }); + expect(wrong).toEqual([]); + }); +}); diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts b/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts index d6a789cca37..59269718d48 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts @@ -80,6 +80,11 @@ import path from 'node:path'; import { BLOCK_CONFIG, type BlockPropField, type PlaceholderSpec } from '../block-config'; import { t } from '../../i18n'; import { uiRoot } from '../../../../../../../scripts/ui-root.mjs'; +// `engine.inspector.pageBlock.*` lives in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); const PREFIX = 'engine.inspector.pageBlock'; @@ -474,7 +479,8 @@ describe('placeholders that must NOT be translated (#3979)', () => { * must LEAVE both tables — and until this describe nothing measured it. The two * repo-wide i18n gates cannot: `check:i18n-keys` and `check:i18n-dead-keys` * both read `packages/i18n/src/locales/`, and `engine.inspector.pageBlock.*` - * lives only in `metadata-admin/i18n.ts` (which says so in its own header). So + * lives only in the designer strings (the `studio` namespace since + * objectstack#25922; `metadata-admin/i18n.ts` says so in its own header). So * each of the four renames so far (objectui#3829, objectui#5212, objectui#8279, * objectui#8278) shipped with a HAND-WRITTEN pin naming the one key it retired * — a convention, not an instrument, and the fifth rename is one forgotten pin @@ -498,6 +504,11 @@ describe('placeholders that must NOT be translated (#3979)', () => { * of the source, the same way `PageBlockInspector.i18n.test.tsx` reads its * own chrome key list rather than hand-copying it. * + * Since objectstack#25922 slice S2 the prefix lives in the `studio` namespace + * of `@object-ui/i18n` (`packages/i18n/src/namespaces/studio/{en,zh}.ts`), so + * a locale's key set is read from BOTH homes — the app-shell table and the + * namespace catalogue — and a key is retired only when it has left both. + * * Measured on this checkout: every one of the 171 `engine.inspector.pageBlock.*` * keys in each table is covered by one of those two, and no key is constructed * dynamically anywhere (`grep` for a template head under this prefix returns @@ -540,6 +551,11 @@ const I18N_SOURCE_PATH = path.join( ROOT, 'packages/app-shell/src/views/metadata-admin/i18n.ts', ); +/** The `studio` namespace's catalogues — the prefix's home since objectstack#25922 S2. */ +const STUDIO_HOME_PATH = { + en: path.join(ROOT, 'packages/i18n/src/namespaces/studio/en.ts'), + zh: path.join(ROOT, 'packages/i18n/src/namespaces/studio/zh.ts'), +}; interface EngineStringTable { /** The `const` name, for assertion messages. */ @@ -565,7 +581,7 @@ interface EngineStringTable { function engineStringTable(source: string, name: string): EngineStringTable { const lines = source.split('\n'); const open = lines.findIndex((l) => l.startsWith(`const ${name}: Record = {`)); - if (open === -1) throw new Error(`${name}: declaration not found in i18n.ts — the extractor is stale`); + if (open === -1) throw new Error(`${name}: declaration not found — the extractor is stale`); const close = lines.findIndex((l, i) => i > open && l === '};'); if (close === -1) throw new Error(`${name}: no column-0 '};' closes the table — the extractor is stale`); const keys: string[] = []; @@ -602,6 +618,7 @@ function shippedLiteralKeys(): Map { if (!/\.(ts|tsx|js|jsx|mjs)$/.test(entry.name)) continue; if (/\.(test|spec)\.[tj]sx?$/.test(entry.name)) continue; if (full === I18N_SOURCE_PATH) continue; // the DEFINITION site, not a reader + if (full === STUDIO_HOME_PATH.en || full === STUDIO_HOME_PATH.zh) continue; // likewise const content = readFileSync(full, 'utf8'); if (!content.includes(PREFIX)) continue; for (const m of content.matchAll(/['"`](engine\.inspector\.pageBlock\.[A-Za-z0-9_.:$-]+)['"`]/g)) { @@ -619,6 +636,15 @@ function shippedLiteralKeys(): Map { const I18N_SOURCE = readFileSync(I18N_SOURCE_PATH, 'utf8'); const EN_TABLE = engineStringTable(I18N_SOURCE, 'ENGINE_STRINGS_EN'); const ZH_TABLE = engineStringTable(I18N_SOURCE, 'ENGINE_STRINGS_ZH'); +const EN_HOME = engineStringTable(readFileSync(STUDIO_HOME_PATH.en, 'utf8'), 'STUDIO_EN'); +const ZH_HOME = engineStringTable(readFileSync(STUDIO_HOME_PATH.zh, 'utf8'), 'STUDIO_ZH'); + +/** One locale's keys across both homes: the app-shell table and the namespace. */ +function bothHomes(table: EngineStringTable, home: EngineStringTable): EngineStringTable { + return { name: `${table.name} + ${home.name}`, keys: [...table.keys, ...home.keys], from: table.from, to: table.to }; +} +const EN_KEYS = bothHomes(EN_TABLE, EN_HOME); +const ZH_KEYS = bothHomes(ZH_TABLE, ZH_HOME); /** The keys `SITES` implies — the complete BLOCK_CONFIG-derived vocabulary. */ const DERIVED = new Set(SITES.map((s) => s.derived)); @@ -656,6 +682,12 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { // set arithmetic below would hide it. expect(EN_TABLE.keys.length).toBe(new Set(EN_TABLE.keys).size); expect(ZH_TABLE.keys.length).toBe(new Set(ZH_TABLE.keys).size); + // The namespace homes are read the same way, and a key in both homes of + // one locale would be counted twice below. + expect(EN_HOME.keys.length).toBeGreaterThan(150); + expect(ZH_HOME.keys.length).toBeGreaterThan(150); + expect(EN_KEYS.keys.length).toBe(new Set(EN_KEYS.keys).size); + expect(ZH_KEYS.keys.length).toBe(new Set(ZH_KEYS.keys).size); }); it('the extractor really sees the keys the derivation implies', () => { @@ -663,8 +695,8 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { // same fact. `SITES` comes from walking BLOCK_CONFIG; these sets come from // parsing the source text. A parser that silently stopped matching (a // reformat, a changed indent) fails here by name, not by going quiet. - const enSet = new Set(EN_TABLE.keys); - const zhSet = new Set(ZH_TABLE.keys); + const enSet = new Set(EN_KEYS.keys); + const zhSet = new Set(ZH_KEYS.keys); expect([...DERIVED].filter((k) => !enSet.has(k))).toEqual([]); expect([...DERIVED].filter((k) => !zhSet.has(k))).toEqual([]); expect(DERIVED.size).toBeGreaterThan(150); @@ -693,8 +725,8 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { // name and the fix is to teach the reader set about it, not to delete the // key. Loud is the correct direction to be wrong in. expect( - unreachable(EN_TABLE), - 'ENGINE_STRINGS_EN keys under this prefix that BLOCK_CONFIG no longer implies and no ' + + unreachable(EN_KEYS), + 'en keys under this prefix that BLOCK_CONFIG no longer implies and no ' + 'shipped source asks for — delete them, or teach the reader set about the new reader', ).toEqual([]); }); @@ -704,8 +736,8 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { // sibling `every key resolves in zh-CN` cannot see it — a LEFTOVER key // resolves perfectly well. expect( - unreachable(ZH_TABLE), - 'ENGINE_STRINGS_ZH keys under this prefix that BLOCK_CONFIG no longer implies and no ' + + unreachable(ZH_KEYS), + 'zh keys under this prefix that BLOCK_CONFIG no longer implies and no ' + 'shipped source asks for — the half a rename forgets', ).toEqual([]); }); @@ -713,10 +745,10 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { it('both tables hold the same pageBlock key set', () => { // "Left BOTH tables", stated directly. Asymmetry here is the signature of a // half-applied retirement, and the message names which side kept the key. - const en = new Set(pageBlockKeys(EN_TABLE)); - const zh = new Set(pageBlockKeys(ZH_TABLE)); - expect([...en].filter((k) => !zh.has(k)), 'in ENGINE_STRINGS_EN but not ZH').toEqual([]); - expect([...zh].filter((k) => !en.has(k)), 'in ENGINE_STRINGS_ZH but not EN').toEqual([]); + const en = new Set(pageBlockKeys(EN_KEYS)); + const zh = new Set(pageBlockKeys(ZH_KEYS)); + expect([...en].filter((k) => !zh.has(k)), 'in the en homes but not zh').toEqual([]); + expect([...zh].filter((k) => !en.has(k)), 'in the zh homes but not en').toEqual([]); expect(en.size).toBeGreaterThan(150); }); }); diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts b/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts index 19db07e6f87..aface69b403 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts @@ -34,6 +34,11 @@ import { } from '../block-config'; import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS, TYPES_BY_CATEGORY } from '../block-types'; import { t } from '../../i18n'; +// `engine.inspector.pageBlock.*` lives in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); describe('block-config', () => { it('exposes a configurable panel for every content block with authorable props', () => { @@ -800,8 +805,9 @@ describe('object-kanban — the `groupBy` control, and the retired `groupField` // The two repo-wide i18n gates do NOT reach this table — `check:i18n-keys` // and `check:i18n-dead-keys` both read `packages/i18n/src/locales/en.ts`, and - // `engine.inspector.pageBlock.*` lives only in `metadata-admin/i18n.ts` - // (which says so in its own header). So the renamed key's two halves are + // `engine.inspector.pageBlock.*` lives only in the designer strings (the + // `studio` namespace since objectstack#25922; `metadata-admin/i18n.ts` says so + // in its own header). So the renamed key's two halves are // pinned here, in the same file as the field it labels: the sibling // `block-config-i18n.test.ts` derives the NEW key from this table's position // and demands both locales define it, and this pin is the other direction — diff --git a/ui/packages/i18n/src/namespaces/studio/en.ts b/ui/packages/i18n/src/namespaces/studio/en.ts index 42fb1a69f9a..39bc00d0abc 100644 --- a/ui/packages/i18n/src/namespaces/studio/en.ts +++ b/ui/packages/i18n/src/namespaces/studio/en.ts @@ -474,6 +474,606 @@ const STUDIO_EN: Record = { // AI assistant entry points 'designer.canvas.askAi': 'Ask AI', 'designer.canvas.askAiGenerate': 'Generate fields with AI', + // engine.inspector — the page block, action, dataset, flow node, app + // navigation and dashboard widget inspectors (#25922 slice S2). + 'engine.inspector.widget.kind': 'Widget', + 'engine.inspector.widget.close': 'Close widget', + 'engine.inspector.widget.title': 'Title', + 'engine.inspector.widget.type': 'Type', + 'engine.inspector.widget.object': 'Data Source (Object)', + 'engine.inspector.widget.valueField': 'Value Field', + 'engine.inspector.widget.categoryField': 'Category Field', + 'engine.inspector.widget.aggregate': 'Aggregate', + 'engine.inspector.widget.color': 'Color Variant', + 'engine.inspector.widget.width': 'Width', + 'engine.inspector.widget.height': 'Height', + 'engine.inspector.widget.remove': 'Remove widget', + // Dataset binding (ADR-0021) — governed cross-object semantic layer. + 'engine.inspector.widget.datasetSection': 'Dataset binding', + 'engine.inspector.widget.dataset': 'Dataset', + 'engine.inspector.widget.datasetPlaceholder': 'e.g. sales_pipeline', + 'engine.inspector.widget.datasetHint': + 'Bind a governed dataset. When set, it takes precedence over the inline object query above and the widget renders via the dataset (consistent, cross-object, RLS-enforced).', + 'engine.inspector.widget.dimensions': 'Dimensions', + 'engine.inspector.widget.dimensionsPlaceholder': 'e.g. stage, region (comma-separated)', + 'engine.inspector.widget.dimensionsHint': + 'Dataset dimension names to group by. Leave empty for a single-value KPI metric.', + 'engine.inspector.widget.values': 'Values (measures)', + 'engine.inspector.widget.valuesPlaceholder': 'e.g. revenue, deal_count (comma-separated)', + 'engine.inspector.widget.valuesHint': 'Dataset measure names to show.', + // Dashboard filter bindings (framework#2501) + 'engine.inspector.widget.filterBindingsSection': 'Dashboard filter bindings', + 'engine.inspector.widget.filterBindingsHint': + 'Map each dashboard-level filter to one of this widget’s own fields, or untick Apply to opt the widget out. Empty = the filter’s own field.', + 'engine.inspector.widget.filterBindingApply': 'Apply', + 'engine.inspector.widget.filterBindingDefault': 'Default ({field})', + 'engine.inspector.widget.filterBindingField': 'Bound field for {filter}', + 'engine.inspector.widget.filterBindingReset': 'Reset', + // objectui#10748 — the widget-type select's options (keyed by the stored + // `type`, which never moves), the untitled-widget fallback and the two + // empty-state messages. + 'engine.inspector.widget.type.metric': 'KPI Metric', + 'engine.inspector.widget.type.bar': 'Bar Chart', + 'engine.inspector.widget.type.horizontalBar': 'Horizontal Bar', + 'engine.inspector.widget.type.line': 'Line Chart', + 'engine.inspector.widget.type.area': 'Area Chart', + 'engine.inspector.widget.type.pie': 'Pie Chart', + 'engine.inspector.widget.type.donut': 'Donut Chart', + 'engine.inspector.widget.type.funnel': 'Funnel', + 'engine.inspector.widget.type.table': 'Table', + 'engine.inspector.widget.type.pivot': 'Pivot Table', + 'engine.inspector.widget.untitledN': 'Widget {n}', + 'engine.inspector.widget.unsupportedSelection': 'Unsupported selection kind: {kind}', + 'engine.inspector.widget.removed': 'The selected widget was removed from the draft.', + // Flow node inspector + 'engine.inspector.flowNode.kind': 'Node', + 'engine.inspector.flowNode.close': 'Close node', + 'engine.inspector.flowNode.id': 'ID', + // objectui#11827 — the ID field's inline refusal of a rename that cannot land; + // the field shows the stored id again, so each says nothing was renamed. + 'engine.inspector.flowNode.idRequired': 'Not renamed: a node needs an id.', + 'engine.inspector.flowNode.idTaken': 'Not renamed: another node in this flow already has the id "{id}".', + 'engine.inspector.flowNode.idEdgeNamed': 'Not renamed: an edge still names "{id}", a node that is no longer in the flow. Remove or reconnect that edge first.', + // objectui#11838 — the rename carries every expression reference to the node + // through the parsers; these name the ones it cannot carry. `{refs}` lists + // each as ` › : `. + 'engine.inspector.flowNode.idRefsUnparsed': 'Not renamed: these expressions read "{id}" but do not parse, so the rename cannot carry them: {refs}. Fix them first.', + 'engine.inspector.flowNode.idRefsAmbiguous': 'Not renamed: "{name}" is also the name of a variable in this flow, so these expressions could read either and cannot be carried: {refs}. Rename the variable first, or edit them by hand.', + // objectui#11838 — a removal cannot carry a boundary event's host or an + // expression root, so it is refused while one names the node (the Remove + // node button and the canvas Delete key both show this). + 'engine.inspector.flowNode.removeRefused': 'Not removed: these still name "{id}" and would name a node that no longer exists: {refs}. Change or remove them first.', + 'engine.inspector.flowNode.label': 'Label', + 'engine.inspector.flowNode.type': 'Node Type', + 'engine.inspector.flowNode.configuration': 'Configuration', + 'engine.inspector.flowNode.config': 'Config (JSON)', + 'engine.inspector.flowNode.advanced': 'Advanced (JSON)', + 'engine.inspector.flowNode.advancedHint': 'Optional custom keys not covered by the form above — most flows don\u2019t need this.', + // objectui#6499 — a dependent field whose controller is off still renders, + // because a stored value is never hidden. These name that state instead of + // letting it read as live configuration. + 'engine.inspector.flowNode.inactiveRetained': 'Kept, not in effect — its controlling field is off. The value stays saved until you clear it.', + 'engine.inspector.flowNode.inactiveRetainedOrphan': 'Kept, not in effect — nothing activates this field. The value stays saved so it is not lost silently.', + 'engine.inspector.flowNode.inactiveRetainedClear': 'Clear value', + // objectui#12779 — an interval or once cadence on a start node, drawn read-only. + 'engine.inspector.flowNode.cadenceLabel': 'Schedule', + 'engine.inspector.flowNode.cadenceEveryMs': 'Every {n} ms', + 'engine.inspector.flowNode.cadenceEverySeconds': 'Every {n} s', + 'engine.inspector.flowNode.cadenceEveryMinutes': 'Every {n} min', + 'engine.inspector.flowNode.cadenceEveryHours': 'Every {n} h', + 'engine.inspector.flowNode.cadenceEveryDays': 'Every {n} d', + 'engine.inspector.flowNode.cadenceOnce': 'Once, at {at}', + 'engine.inspector.flowNode.cadenceReadOnly': 'This flow runs on an interval or at a set time, not on a cron. The designer edits cron schedules only, so this schedule is shown as stored and kept unchanged.', + 'engine.inspector.flowNode.noConfig': 'No configuration needed for this node type.', + 'engine.inspector.flowNode.nestedIdHint': 'A node inside a container region keeps its id here — rename it in the container’s Advanced JSON.', + // objectui#10748 — the nested-node breadcrumb's accessible name, and the + // Advanced (JSON) box's refusal of a value that parses but is not an object. + 'engine.inspector.flowNode.nestedLocation': 'nested node location', + 'engine.inspector.flowNode.advancedNotObject': 'Must be a JSON object', + 'engine.inspector.flowNode.kv.add': 'Add entry', + 'engine.inspector.flowNode.kv.key': 'Key', + 'engine.inspector.flowNode.kv.value': 'Value', + 'engine.inspector.flowNode.kv.remove': 'Remove entry', + 'engine.inspector.flowNode.kv.empty': 'No entries yet.', + // objectui#7588 — the per-value text / expression toggle on a `value`-role + // map (the assignment node's `assignments`); see `flow-value-envelope.ts`. + 'engine.inspector.flowNode.kv.asExpression': 'Write as a CEL expression', + 'engine.inspector.flowNode.kv.expressionPlaceholder': 'CEL expression, e.g. joinNonEmpty(…)', + 'engine.inspector.flowNode.list.add': 'Add item', + 'engine.inspector.flowNode.list.item': 'Value', + 'engine.inspector.flowNode.list.remove': 'Remove item', + 'engine.inspector.flowNode.list.empty': 'No items yet.', + 'engine.inspector.flowNode.branch.add': 'Add branch', + 'engine.inspector.flowNode.branch.fromEdges': 'This decision routes on its connections’ conditions. Each card is one connection; edit its condition on the connection.', + 'engine.inspector.flowNode.branch.openEdge': 'Edit connection', + 'engine.inspector.flowNode.branch.noCondition': 'No condition', + 'engine.inspector.flowNode.remove': 'Remove node', + // App nav inspector + 'engine.inspector.appNav.kind': 'Nav Item', + 'engine.inspector.appNav.close': 'Close nav item', + 'engine.inspector.appNav.label': 'Label', + 'engine.inspector.appNav.icon': 'Icon', + 'engine.inspector.appNav.remove': 'Remove nav item', + 'engine.inspector.appNav.typeField': 'Type', + 'engine.inspector.appNav.type.object': 'Object', + 'engine.inspector.appNav.type.page': 'Page', + 'engine.inspector.appNav.type.dashboard': 'Dashboard', + 'engine.inspector.appNav.type.report': 'Report', + 'engine.inspector.appNav.type.url': 'Link (URL)', + 'engine.inspector.appNav.type.group': 'Group', + 'engine.inspector.appNav.object': 'Object', + 'engine.inspector.appNav.targetMode': 'Landing', + 'engine.inspector.appNav.mode.default': 'Default view (workspace)', + 'engine.inspector.appNav.mode.view': 'Named view', + 'engine.inspector.appNav.mode.record': 'Record deep-link', + 'engine.inspector.appNav.mode.filters': 'Filtered slice (/data)', + 'engine.inspector.appNav.view': 'View', + 'engine.inspector.appNav.recordId': 'Record ID', + 'engine.inspector.appNav.recordIdHint': 'Supports {current_user_id} and {current_org_id} template variables.', + 'engine.inspector.appNav.recordMode': 'Open mode', + 'engine.inspector.appNav.recordModeView': 'View', + 'engine.inspector.appNav.recordModeEdit': 'Edit', + 'engine.inspector.appNav.filters': 'URL filters', + 'engine.inspector.appNav.filtersField': 'Field', + 'engine.inspector.appNav.filtersValue': 'Value', + 'engine.inspector.appNav.filtersAdd': 'Add condition', + 'engine.inspector.appNav.filtersRemove': 'Remove condition', + 'engine.inspector.appNav.filtersHint': 'One-off slice carried in the URL — promote it to a named view when it becomes curated and reused.', + 'engine.inspector.appNav.url': 'URL', + 'engine.inspector.appNav.urlTarget': 'Open in', + 'engine.inspector.appNav.urlTargetSelf': 'Same tab', + 'engine.inspector.appNav.urlTargetBlank': 'New tab', + 'engine.inspector.appNav.preview': 'Resolved link', + // objectui#11790 — the Studio nav editor's Type choice offers every member + // of the spec's nav-item union; these are the members, and the pickers' + // texts, the rows above did not carry. + 'engine.inspector.appNav.type.action': 'Action', + 'engine.inspector.appNav.type.component': 'Component', + 'engine.inspector.appNav.type.doc': 'Documentation', + 'engine.inspector.appNav.type.separator': 'Separator', + 'engine.inspector.appNav.choose': '— Choose —', + 'engine.inspector.appNav.noTargets': 'There is nothing of this type to link to yet.', + 'engine.inspector.appNav.unboundHint': 'Until a target is chosen, this item stays here but is left out of the saved navigation.', + 'engine.inspector.appNav.actionHint': 'Only actions not bound to an object can run from the menu.', + 'engine.inspector.appNav.docPage': 'Doc page', + 'engine.inspector.appNav.book': 'Book', + 'engine.inspector.appNav.docHint': 'Choose a doc page, a book, or both.', + 'engine.inspector.appNav.groupHint': 'A group is a titled section of the menu. Items are not nested under it from this editor.', + 'engine.inspector.appNav.separatorHint': 'A separator is a divider: it has no label and opens nothing.', + // Page block inspector + 'engine.inspector.pageBlock.kind': 'Block', + 'engine.inspector.pageBlock.close': 'Close block', + 'engine.inspector.pageBlock.type': 'Type', + 'engine.inspector.pageBlock.id': 'ID', + 'engine.inspector.pageBlock.className': 'Class names', + 'engine.inspector.pageBlock.visibleWhen': 'Visible when (CEL)', + 'engine.inspector.pageBlock.properties': 'Properties', + 'engine.inspector.pageBlock.advanced': 'Advanced', + 'engine.inspector.pageBlock.remove': 'Remove block', + 'engine.inspector.pageBlock.outlineLabel': 'Blocks', + // Chrome of the panel's own list/JSON editors (#3963) — NOT block-config + // data. These live beside the keys above (not under `.field.`, which is + // reserved for keys derived from a BLOCK_CONFIG position) because they are + // one shared control repeated across every list field, with no per-field + // wording. `list.*` mirrors `engine.inspector.flowNode.list.*`, the same + // editor shape in a sibling panel. + 'engine.inspector.pageBlock.list.add': 'Add', + 'engine.inspector.pageBlock.list.remove': 'Remove', + 'engine.inspector.pageBlock.list.removeItem': 'Remove item', + 'engine.inspector.pageBlock.objectPlaceholder': 'snake_case object', + 'engine.inspector.pageBlock.fieldPlaceholder': 'field name', + // Page block inspector — curated property labels (#3913) and the prose + // placeholders (#3979). + // These are the `label` / `addLabel` / option-label values of + // `previews/block-config.ts`; that file stores the KEY and the inspector + // resolves it through `t()` at render. Key shape is derived from the label's + // position in BLOCK_CONFIG and pinned by + // `previews/__tests__/block-config-i18n.test.ts` — see that file's header. + // + // `placeholder.*` is the same derivation for a field's input hint, and it is + // deliberately only PART of that column: a placeholder holding an example + // VALUE (`20`, `https://…`, a JSON sample) stays a literal in block-config and + // has no key here, because translating it would corrupt what the author is + // being told to type. `block-config.ts` declares which kind each one is. + 'engine.inspector.pageBlock.field.object-grid.objectName': 'Object', + 'engine.inspector.pageBlock.field.object-grid.columns': 'Columns', + 'engine.inspector.pageBlock.field.object-grid.pageSize': 'Page size', + 'engine.inspector.pageBlock.field.object-form.objectName': 'Object', + 'engine.inspector.pageBlock.field.object-form.mode': 'Mode', + 'engine.inspector.pageBlock.option.mode.create': 'Create', + 'engine.inspector.pageBlock.option.mode.edit': 'Edit', + 'engine.inspector.pageBlock.option.mode.view': 'View', + 'engine.inspector.pageBlock.field.object-form.formType': 'Form type', + 'engine.inspector.pageBlock.option.formType.simple': 'Simple', + 'engine.inspector.pageBlock.option.formType.tabbed': 'Tabbed', + 'engine.inspector.pageBlock.option.formType.wizard': 'Wizard', + 'engine.inspector.pageBlock.option.formType.split': 'Split', + 'engine.inspector.pageBlock.option.formType.drawer': 'Drawer', + 'engine.inspector.pageBlock.option.formType.modal': 'Modal', + 'engine.inspector.pageBlock.field.object-form.layout': 'Layout', + 'engine.inspector.pageBlock.option.layout.vertical': 'Vertical', + 'engine.inspector.pageBlock.option.layout.horizontal': 'Horizontal', + 'engine.inspector.pageBlock.field.object-form.columns': 'Field columns (1–4)', + 'engine.inspector.pageBlock.field.object-form.fields': 'Fields', + 'engine.inspector.pageBlock.field.object-form.title': 'Title', + 'engine.inspector.pageBlock.field.object-form.description': 'Description', + 'engine.inspector.pageBlock.field.object-metric.objectName': 'Object', + 'engine.inspector.pageBlock.field.object-metric.label': 'Label', + 'engine.inspector.pageBlock.field.object-metric.description': 'Description', + 'engine.inspector.pageBlock.field.object-metric.icon': 'Icon', + 'engine.inspector.pageBlock.placeholder.object-metric.icon': 'lucide icon name', + 'engine.inspector.pageBlock.field.object-metric.colorVariant': 'Color', + 'engine.inspector.pageBlock.option.colorVariant.default': 'Default', + 'engine.inspector.pageBlock.option.colorVariant.blue': 'Blue', + 'engine.inspector.pageBlock.option.colorVariant.teal': 'Teal', + 'engine.inspector.pageBlock.option.colorVariant.orange': 'Orange', + 'engine.inspector.pageBlock.option.colorVariant.purple': 'Purple', + 'engine.inspector.pageBlock.option.colorVariant.success': 'Success', + 'engine.inspector.pageBlock.option.colorVariant.warning': 'Warning', + 'engine.inspector.pageBlock.option.colorVariant.danger': 'Danger', + 'engine.inspector.pageBlock.field.object-metric.format': 'Format', + 'engine.inspector.pageBlock.option.format.number': 'Number', + 'engine.inspector.pageBlock.option.format.currency': 'Currency', + 'engine.inspector.pageBlock.option.format.percent': 'Percent', + 'engine.inspector.pageBlock.field.object-metric.prefix': 'Prefix', + 'engine.inspector.pageBlock.field.object-metric.suffix': 'Suffix', + 'engine.inspector.pageBlock.field.object-kanban.objectName': 'Object', + // `…object-kanban.groupField` was renamed to `…groupBy` with the control it + // labels (objectui#7772): the lane picker wrote a key `ObjectKanban.tsx` + // never reads and objectui#7322 retired by name, while the REQUIRED `groupBy` + // had no control. The English text is deliberately unchanged — the box means + // exactly what it always meant; only the key its position implies moved. + 'engine.inspector.pageBlock.field.object-kanban.groupBy': 'Group by field', + 'engine.inspector.pageBlock.field.object-kanban.titleField': 'Title field', + 'engine.inspector.pageBlock.field.object-kanban.cardFields': 'Card fields', + 'engine.inspector.pageBlock.field.object-kanban.limit': 'Limit', + 'engine.inspector.pageBlock.field.grid.columns': 'Columns', + 'engine.inspector.pageBlock.field.grid.gap': 'Gap', + 'engine.inspector.pageBlock.field.element:text.content': 'Content', + 'engine.inspector.pageBlock.placeholder.element:text.content': 'Text…', + 'engine.inspector.pageBlock.field.element:text.variant': 'Variant', + 'engine.inspector.pageBlock.option.variant.h1': 'Heading 1', + 'engine.inspector.pageBlock.option.variant.h2': 'Heading 2', + 'engine.inspector.pageBlock.option.variant.h3': 'Heading 3', + 'engine.inspector.pageBlock.option.variant.h4': 'Heading 4', + 'engine.inspector.pageBlock.option.variant.h5': 'Heading 5', + 'engine.inspector.pageBlock.option.variant.h6': 'Heading 6', + 'engine.inspector.pageBlock.option.variant.body': 'Body', + 'engine.inspector.pageBlock.option.variant.caption': 'Caption', + 'engine.inspector.pageBlock.option.variant.overline': 'Overline', + 'engine.inspector.pageBlock.field.element:text.align': 'Align', + 'engine.inspector.pageBlock.option.align.left': 'Left', + 'engine.inspector.pageBlock.option.align.center': 'Center', + 'engine.inspector.pageBlock.option.align.right': 'Right', + 'engine.inspector.pageBlock.field.element:image.src': 'Source URL', + 'engine.inspector.pageBlock.field.element:image.alt': 'Alt text', + 'engine.inspector.pageBlock.field.element:image.fit': 'Fit', + 'engine.inspector.pageBlock.option.fit.cover': 'Cover', + 'engine.inspector.pageBlock.option.fit.contain': 'Contain', + 'engine.inspector.pageBlock.option.fit.fill': 'Fill', + 'engine.inspector.pageBlock.field.element:definition-list.items': 'Items', + 'engine.inspector.pageBlock.add.element:definition-list.items': 'Add item', + 'engine.inspector.pageBlock.field.element:definition-list.items.term': 'Term', + 'engine.inspector.pageBlock.field.element:definition-list.items.description': 'Description', + 'engine.inspector.pageBlock.field.element:definition-list.columns': 'Columns (1 or 2)', + 'engine.inspector.pageBlock.field.element:definition-list.inline': 'Inline (term · description)', + 'engine.inspector.pageBlock.field.element:repeater.object': 'Object', + 'engine.inspector.pageBlock.field.element:repeater.titleField': 'Title field', + 'engine.inspector.pageBlock.field.element:repeater.fields': 'Fields', + 'engine.inspector.pageBlock.field.element:repeater.limit': 'Limit', + 'engine.inspector.pageBlock.field.element:repeater.emptyText': 'Empty text', + 'engine.inspector.pageBlock.field.element:repeater.divided': 'Dividers between rows', + 'engine.inspector.pageBlock.field.element:number.object': 'Object', + 'engine.inspector.pageBlock.field.element:number.field': 'Field', + 'engine.inspector.pageBlock.field.element:number.aggregate': 'Aggregate', + 'engine.inspector.pageBlock.option.aggregate.count': 'Count', + 'engine.inspector.pageBlock.option.aggregate.sum': 'Sum', + 'engine.inspector.pageBlock.option.aggregate.avg': 'Average', + 'engine.inspector.pageBlock.option.aggregate.min': 'Min', + 'engine.inspector.pageBlock.option.aggregate.max': 'Max', + 'engine.inspector.pageBlock.field.element:number.format': 'Format', + 'engine.inspector.pageBlock.field.element:number.prefix': 'Prefix', + 'engine.inspector.pageBlock.field.element:number.suffix': 'Suffix', + 'engine.inspector.pageBlock.field.element:button.label': 'Label', + 'engine.inspector.pageBlock.field.element:button.variant': 'Variant', + 'engine.inspector.pageBlock.option.variant.primary': 'Primary', + 'engine.inspector.pageBlock.option.variant.secondary': 'Secondary', + 'engine.inspector.pageBlock.option.variant.danger': 'Danger', + 'engine.inspector.pageBlock.option.variant.ghost': 'Ghost', + 'engine.inspector.pageBlock.option.variant.link': 'Link', + 'engine.inspector.pageBlock.field.element:button.size': 'Size', + 'engine.inspector.pageBlock.option.size.small': 'Small', + 'engine.inspector.pageBlock.option.size.medium': 'Medium', + 'engine.inspector.pageBlock.option.size.large': 'Large', + 'engine.inspector.pageBlock.field.element:button.icon': 'Icon', + 'engine.inspector.pageBlock.placeholder.element:button.icon': 'lucide icon name', + 'engine.inspector.pageBlock.field.element:button.action': 'Action', + 'engine.inspector.pageBlock.field.page:header.title': 'Title', + 'engine.inspector.pageBlock.field.page:header.subtitle': 'Subtitle', + // `…field.page:header.icon` and its placeholder retired with the spec key + // itself (objectstack#6946 / PR objectstack#7115, @objectstack/spec + // 17.0.0-rc.6). Their only reader was the canonical `page:header` icon field + // removed from `previews/block-config.ts`; a key kept past its field is dead + // vocabulary that the next author reads as a live surface. objectui#3829. + // `…field.page:header.breadcrumb` left the same way with the designer's + // breadcrumb toggle (objectui#11173; the spec key retires through + // objectstack#20758). + 'engine.inspector.pageBlock.field.page:card.title': 'Title', + 'engine.inspector.pageBlock.field.page:card.bordered': 'Bordered', + 'engine.inspector.pageBlock.field.page:tabs.items': 'Tabs', + 'engine.inspector.pageBlock.add.page:tabs.items': 'Add tab', + 'engine.inspector.pageBlock.field.page:tabs.items.value': 'Value', + 'engine.inspector.pageBlock.field.page:tabs.items.label': 'Label', + // `…field.page:accordion.title` and `…field.page:accordion.items.value` + // retired with their designer fields (objectui#5212): neither is read by + // `PageAccordionRenderer` nor declared by `PageAccordionProps`, so a key kept + // past its field is dead vocabulary the next author reads as a live surface. + 'engine.inspector.pageBlock.field.page:accordion.items': 'Sections', + 'engine.inspector.pageBlock.add.page:accordion.items': 'Add section', + 'engine.inspector.pageBlock.field.page:accordion.items.label': 'Label', + 'engine.inspector.pageBlock.field.record:related_list.objectName': 'Object', + 'engine.inspector.pageBlock.field.record:related_list.relationshipField': 'Relationship field', + 'engine.inspector.pageBlock.field.record:related_list.title': 'Title', + 'engine.inspector.pageBlock.field.record:related_list.limit': 'Limit', + 'engine.inspector.pageBlock.field.record:highlights.fields': 'Fields', + 'engine.inspector.pageBlock.field.record:details.sections': 'Sections', + 'engine.inspector.pageBlock.add.record:details.sections': 'Add section', + 'engine.inspector.pageBlock.field.record:details.sections.name': 'Name (i18n key)', + 'engine.inspector.pageBlock.placeholder.record:details.sections.name': 'snake_case, e.g. contact_info', + 'engine.inspector.pageBlock.field.record:details.sections.label': 'Label', + 'engine.inspector.pageBlock.field.record:details.sections.columns': 'Columns', + 'engine.inspector.pageBlock.field.record:details.sections.fields': 'Fields', + 'engine.inspector.pageBlock.field.record:alert.severity': 'Severity', + 'engine.inspector.pageBlock.option.severity.info': 'Info', + 'engine.inspector.pageBlock.option.severity.warning': 'Warning', + 'engine.inspector.pageBlock.option.severity.error': 'Error', + 'engine.inspector.pageBlock.option.severity.success': 'Success', + 'engine.inspector.pageBlock.field.record:alert.title': 'Title', + 'engine.inspector.pageBlock.field.record:alert.body': 'Body', + 'engine.inspector.pageBlock.field.record:alert.icon': 'Icon', + 'engine.inspector.pageBlock.placeholder.record:alert.icon': 'lucide icon name', + 'engine.inspector.pageBlock.field.record:alert.dismissible': 'Dismissible', + 'engine.inspector.pageBlock.field.record:path.statusField': 'Status field', + 'engine.inspector.pageBlock.field.record:path.stages': 'Stages', + 'engine.inspector.pageBlock.add.record:path.stages': 'Add stage', + 'engine.inspector.pageBlock.field.record:path.stages.value': 'Value', + 'engine.inspector.pageBlock.field.record:path.stages.label': 'Label', + 'engine.inspector.pageBlock.field.record:quick_actions.actionNames': 'Action names', + 'engine.inspector.pageBlock.placeholder.record:quick_actions.actionNames': 'action name', + 'engine.inspector.pageBlock.field.record:quick_actions.location': 'Location', + 'engine.inspector.pageBlock.option.location.record_header': 'Record header', + 'engine.inspector.pageBlock.option.location.record_more': 'Record more menu', + 'engine.inspector.pageBlock.option.location.record_section': 'Record section', + 'engine.inspector.pageBlock.option.location.record_related': 'Record related', + 'engine.inspector.pageBlock.option.location.list_toolbar': 'List toolbar', + 'engine.inspector.pageBlock.option.location.list_item': 'List item', + // `…option.location.global_nav` retired with the location itself + // (objectstack#6888, @objectstack/spec 17.0.0-rc.6). Its only reader was the + // dropdown option removed from `previews/block-config.ts`; a key kept past its + // option is dead vocabulary that the next author reads as a live surface. + // Action default (home) inspector + 'engine.inspector.action.kind': 'Action', + 'engine.inspector.action.close': 'Close action', + // Action default (home) inspector (objectui#10586). Option labels are keyed + // by the stored value; `{…}` tokens are slots the inspector fills with a + // node (a code span, a capability list). HTTP methods, `↑` / `↓` and the + // path / dotted-key samples stay literal. + 'engine.inspector.action.type.script': 'Script — run an expression / sandboxed JS', + 'engine.inspector.action.type.api': 'API — call an endpoint', + 'engine.inspector.action.type.flow': 'Flow — invoke a flow', + 'engine.inspector.action.type.modal': 'Modal — open a modal/page', + 'engine.inspector.action.type.form': 'Form — open a FormView', + 'engine.inspector.action.type.url': 'URL — navigate to a link', + 'engine.inspector.action.variant.primary': 'Primary', + 'engine.inspector.action.variant.secondary': 'Secondary', + 'engine.inspector.action.variant.danger': 'Danger', + 'engine.inspector.action.variant.ghost': 'Ghost', + 'engine.inspector.action.variant.link': 'Link', + 'engine.inspector.action.component.button': 'Button', + 'engine.inspector.action.component.icon': 'Icon only', + 'engine.inspector.action.component.menu': 'Menu item', + 'engine.inspector.action.component.group': 'Button group', + 'engine.inspector.action.mode.create': 'Create', + 'engine.inspector.action.mode.edit': 'Edit', + 'engine.inspector.action.mode.delete': 'Delete', + 'engine.inspector.action.mode.custom': 'Custom', + 'engine.inspector.action.bodyLang.expression': 'Expression (L1)', + 'engine.inspector.action.bodyLang.js': 'Sandboxed JS (L2)', + 'engine.inspector.action.paramType.text': 'Text', + 'engine.inspector.action.paramType.textarea': 'Long text', + 'engine.inspector.action.paramType.number': 'Number', + 'engine.inspector.action.paramType.boolean': 'Checkbox', + 'engine.inspector.action.paramType.select': 'Select', + 'engine.inspector.action.paramType.date': 'Date', + 'engine.inspector.action.paramType.datetime': 'Date/time', + 'engine.inspector.action.paramType.lookup': 'Lookup', + 'engine.inspector.action.location.record_header': 'Record header', + 'engine.inspector.action.location.record_more': 'Record · more menu', + 'engine.inspector.action.location.record_section': 'Record · section', + 'engine.inspector.action.location.record_related': 'Record · related list', + 'engine.inspector.action.location.list_toolbar': 'List toolbar', + 'engine.inspector.action.location.list_item': 'List · row', + 'engine.inspector.action.target.url.label': 'URL', + 'engine.inspector.action.target.url.placeholder': 'https://… or /path?x=${param.x}', + 'engine.inspector.action.target.url.hint': 'Supports ${param.x} and ${ctx.x} interpolation.', + 'engine.inspector.action.target.flow.label': 'Flow name', + 'engine.inspector.action.target.flow.placeholder': 'snake_case flow', + 'engine.inspector.action.target.flow.hint': 'The flow to invoke when clicked.', + 'engine.inspector.action.target.modal.label': 'Modal / page name', + 'engine.inspector.action.target.modal.placeholder': 'snake_case page', + 'engine.inspector.action.target.modal.hint': 'The modal or page to open.', + 'engine.inspector.action.target.form.label': 'Form view name', + 'engine.inspector.action.target.form.hint': 'Opens /console/forms/.', + 'engine.inspector.action.target.api.label': 'API endpoint', + 'engine.inspector.action.target.api.hint': 'Endpoint called with the request body below.', + 'engine.inspector.action.scriptOp.run': 'Run a script — sandboxed JS / expression body', + 'engine.inspector.action.scriptOp.update': 'Update fields on this record — no code', + 'engine.inspector.action.objectPlaceholder': 'snake_case object', + 'engine.inspector.action.objectNone': '— None (global) —', + 'engine.inspector.action.options': 'Options', + 'engine.inspector.action.optionsEmpty': 'No choices yet — a Select with no options opens an empty picker in the dialog.', + 'engine.inspector.action.optionLabel': 'Label', + 'engine.inspector.action.optionValue': 'Value', + 'engine.inspector.action.removeOption': 'Remove option {index}', + 'engine.inspector.action.addOption': 'Add option', + 'engine.inspector.action.basics': 'Basics', + 'engine.inspector.action.label': 'Label', + 'engine.inspector.action.labelPlaceholder': 'Button text shown to users', + 'engine.inspector.action.name': 'Name', + 'engine.inspector.action.namePlaceholder': 'snake_case identifier', + 'engine.inspector.action.object': 'Object', + 'engine.inspector.action.objectBoundHint': 'Bound action — surfaces in this object’s views per the placement below.', + 'engine.inspector.action.objectGlobalHint': 'Empty = global action — must be referenced by a page’s quick actions, global nav, a flow, or AI to appear.', + 'engine.inspector.action.icon': 'Icon', + 'engine.inspector.action.variant': 'Variant', + 'engine.inspector.action.behavior': 'Behavior', + 'engine.inspector.action.behaviorHint': 'What happens when the action is triggered.', + 'engine.inspector.action.type': 'Type', + 'engine.inspector.action.pinnedScript': 'Pinned to {script}: the field write is performed on the platform action route, which is this type’s own route. Any other type is refused beside it.', + 'engine.inspector.action.whatItDoes': 'What it does', + 'engine.inspector.action.fieldValues': 'Field values', + 'engine.inspector.action.fieldValuesEmpty': 'No field values yet — add one, or collect the value as an input below.', + 'engine.inspector.action.fieldValueField': 'Field', + 'engine.inspector.action.fieldValueValue': 'Value', + 'engine.inspector.action.removeFieldValue': 'Remove field value', + 'engine.inspector.action.addFieldValue': 'Add field value', + 'engine.inspector.action.updateHint': 'Written to the current record as the caller — object permissions, hooks and validations fire as for a user edit. An input collected below with the same name overrides the fixed value here.', + 'engine.inspector.action.scriptLanguage': 'Script language', + 'engine.inspector.action.scriptBody': 'Script body', + 'engine.inspector.action.scriptBodyHint': 'Runs in the sandbox as {signature}.', + 'engine.inspector.action.method': 'Method', + 'engine.inspector.action.inputs': 'Inputs', + 'engine.inspector.action.inputsHint': 'Collected from the user in a dialog before the action runs.', + 'engine.inspector.action.inputsEmpty': 'No inputs — the action runs immediately on click.', + 'engine.inspector.action.removeInput': 'Remove input', + 'engine.inspector.action.bindToField': 'Bind to field', + 'engine.inspector.action.paramName': 'Name', + 'engine.inspector.action.paramNamePlaceholder': 'request-body key', + 'engine.inspector.action.paramLabel': 'Label', + 'engine.inspector.action.paramTypeLabel': 'Type', + 'engine.inspector.action.paramPlaceholder': 'Placeholder', + 'engine.inspector.action.paramRequired': 'Required', + 'engine.inspector.action.paramPrefill': 'Pre-fill from row', + 'engine.inspector.action.addInput': 'Add input', + 'engine.inspector.action.placement': 'Placement', + 'engine.inspector.action.placementHint': 'Where this action surfaces in the UI.', + 'engine.inspector.action.noPlacement': 'No placement selected — this action will not appear on any record or list surface. Tick a placement above, or place it from a view’s bulk actions.', + 'engine.inspector.action.capabilityGate': 'Capability-gated on {capabilities} — every placement ticked above hides this action from anyone who does not hold {whom}. Not greyed out and not an error: the button is simply absent, and the server refuses the invocation with a 403 either way.', + 'engine.inspector.action.capabilityGateAll': 'all of them', + 'engine.inspector.action.capabilityGateOne': 'it', + 'engine.inspector.action.capabilityGateSelf': 'This session does not hold {capabilities}, so the button is hidden from you too — grant it through a permission set to see it in the app.', + 'engine.inspector.action.component': 'Component', + 'engine.inspector.action.feedback': 'Feedback', + 'engine.inspector.action.feedbackHint': 'Confirmation and post-run messaging.', + 'engine.inspector.action.confirmText': 'Confirm prompt', + 'engine.inspector.action.confirmTextPlaceholder': 'Ask before running (leave blank to skip)', + 'engine.inspector.action.successMessage': 'Success message', + 'engine.inspector.action.errorMessage': 'Error message', + 'engine.inspector.action.mode': 'Mode', + 'engine.inspector.action.refreshAfter': 'Refresh view after', + 'engine.inspector.action.undoable': 'Offer undo', + 'engine.inspector.action.conditions': 'Conditions', + 'engine.inspector.action.conditionsHint': 'No-code predicates over the record / user / ctx (compiled to CEL).', + 'engine.inspector.action.visibleWhen': 'Visible when', + 'engine.inspector.action.disabledWhen': 'Disabled when', + 'engine.inspector.action.ai': 'AI exposure', + 'engine.inspector.action.aiHint': 'Opt-in: expose this action to AI agents as a callable tool.', + 'engine.inspector.action.aiExposed': 'Expose to AI agents', + 'engine.inspector.action.aiDescription': 'Tool description (required, ≥40 chars)', + 'engine.inspector.action.aiDescriptionPlaceholder': 'When and why an agent should call this action…', + 'engine.inspector.action.aiDescriptionTooShort': 'A ≥40-character description is required while exposed.', + 'engine.inspector.action.moreFieldsHint': 'Advanced / rarely-used properties.', + // Dataset default (home) inspector (objectui#10586). Option labels are keyed + // by the stored value; `{…}` tokens in a sentence are slots the inspector + // fills with a node (a code span, a count). + 'engine.inspector.dataset.kind': 'Dataset', + 'engine.inspector.dataset.aggregate.count': 'count', + 'engine.inspector.dataset.aggregate.sum': 'sum', + 'engine.inspector.dataset.aggregate.avg': 'avg', + 'engine.inspector.dataset.aggregate.min': 'min', + 'engine.inspector.dataset.aggregate.max': 'max', + 'engine.inspector.dataset.aggregate.count_distinct': 'count distinct', + 'engine.inspector.dataset.dimType.string': 'string', + 'engine.inspector.dataset.dimType.number': 'number', + 'engine.inspector.dataset.dimType.date': 'date', + 'engine.inspector.dataset.dimType.boolean': 'boolean', + 'engine.inspector.dataset.dimType.lookup': 'lookup', + 'engine.inspector.dataset.granularity.none': '— none —', + 'engine.inspector.dataset.granularity.day': 'day', + 'engine.inspector.dataset.granularity.week': 'week', + 'engine.inspector.dataset.granularity.month': 'month', + 'engine.inspector.dataset.granularity.quarter': 'quarter', + 'engine.inspector.dataset.granularity.year': 'year', + 'engine.inspector.dataset.derivedOp.ratio': 'ratio (a ÷ b)', + 'engine.inspector.dataset.derivedOp.sum': 'sum (a + b)', + 'engine.inspector.dataset.derivedOp.difference': 'difference (a − b)', + 'engine.inspector.dataset.derivedOp.product': 'product (a × b)', + 'engine.inspector.dataset.formatKind.raw': 'Raw number', + 'engine.inspector.dataset.formatKind.number': 'Number — 1,234.5', + 'engine.inspector.dataset.formatKind.currency': 'Currency — $1,234.50', + 'engine.inspector.dataset.formatKind.percent': 'Percent — 12.3%', + 'engine.inspector.dataset.usage.bound': 'Bound by {reports} · {dashboards} — changes affect them.', + 'engine.inspector.dataset.usage.reportsOne': '{count} report', + 'engine.inspector.dataset.usage.reportsOther': '{count} reports', + 'engine.inspector.dataset.usage.dashboardsOne': '{count} dashboard', + 'engine.inspector.dataset.usage.dashboardsOther': '{count} dashboards', + 'engine.inspector.dataset.usage.unbound': 'Not yet bound by any report or dashboard.', + 'engine.inspector.dataset.name': 'Name', + 'engine.inspector.dataset.namePlaceholder': 'snake_case identifier', + 'engine.inspector.dataset.label': 'Label', + 'engine.inspector.dataset.description': 'Description', + 'engine.inspector.dataset.baseObject': 'Base object', + 'engine.inspector.dataset.baseObjectPlaceholder': 'Select an object…', + 'engine.inspector.dataset.searchObjects': 'Search objects…', + 'engine.inspector.dataset.baseObjectChangeHint': 'Changing the base object clears its dimensions, field measures, joins & filters; a row count is kept.', + 'engine.inspector.dataset.includedRelationships': 'Included relationships', + 'engine.inspector.dataset.noJoins': 'No joins. Add a relationship (a lookup field on {object}) to use {path} dimensions/measures.', + 'engine.inspector.dataset.baseObjectFallback': 'the base object', + 'engine.inspector.dataset.includedRelationship': 'Included relationship', + 'engine.inspector.dataset.relationshipPlaceholder': 'Select a relationship…', + 'engine.inspector.dataset.searchRelationships': 'Search relationships…', + 'engine.inspector.dataset.removeRelationship': 'Remove relationship', + 'engine.inspector.dataset.scopeFilter': 'Scope filter', + 'engine.inspector.dataset.scopeFilterHelp': 'Intrinsic scope, ANDed into every query (e.g. exclude soft-deleted records).', + 'engine.inspector.dataset.dimensions': 'Dimensions', + 'engine.inspector.dataset.addDimension': 'Add dimension', + 'engine.inspector.dataset.dimensionN': 'Dimension {index}', + 'engine.inspector.dataset.removeDimension': 'Remove dimension', + 'engine.inspector.dataset.dimensionNamePlaceholder': 'e.g. region', + 'engine.inspector.dataset.field': 'Field', + 'engine.inspector.dataset.dimensionFieldPlaceholder': 'field or relationship.field', + 'engine.inspector.dataset.type': 'Type', + 'engine.inspector.dataset.labelOptional': 'Label (optional)', + 'engine.inspector.dataset.displayLabel': 'Display label', + 'engine.inspector.dataset.dateBucket': 'Date bucket', + 'engine.inspector.dataset.measures': 'Measures', + 'engine.inspector.dataset.addMeasure': 'Add measure', + 'engine.inspector.dataset.measureN': 'Measure {index}', + 'engine.inspector.dataset.removeMeasure': 'Remove measure', + 'engine.inspector.dataset.measureNamePlaceholder': 'e.g. revenue', + 'engine.inspector.dataset.aggregate': 'Aggregate', + 'engine.inspector.dataset.measureFieldPlaceholder': 'field (optional for count)', + 'engine.inspector.dataset.measureFilter': 'Filter (measure-scoped)', + 'engine.inspector.dataset.measureFilterHelp': 'Only rows matching this filter feed this measure (e.g. won_amount = sum(amount) where stage = won).', + 'engine.inspector.dataset.derived': 'Derived — computed from other measures', + 'engine.inspector.dataset.operation': 'Operation', + 'engine.inspector.dataset.operands': 'Operands (other measures)', + 'engine.inspector.dataset.derivedNeedTwo': 'Select exactly 2 measures for {op}.', + 'engine.inspector.dataset.derivedNeedOne': 'Select at least 1 measure for {op}.', + 'engine.inspector.dataset.addOtherMeasuresFirst': 'Add other measures first.', + 'engine.inspector.dataset.advanced': 'Advanced', + 'engine.inspector.dataset.displayFormat': 'Display format', + 'engine.inspector.dataset.decimals': 'Decimals', + 'engine.inspector.dataset.currency': 'Currency', + 'engine.inspector.dataset.sample': 'Sample: {sample}', + 'engine.inspector.dataset.relWarning': "Relationship {rel} isn't in Included relationships.", + 'engine.inspector.dataset.relWarningAdd': 'Add it', + 'engine.inspector.dataset.filterAdvanced': 'Advanced filter (nested / OR) — edit it in the {source} tab.', + 'engine.inspector.dataset.sourceTab': 'Source', + 'engine.inspector.dataset.filterConditionsOne': '{count} condition', + 'engine.inspector.dataset.filterConditionsOther': '{count} conditions', + 'engine.inspector.dataset.addFilter': '+ Add filter…', + 'engine.inspector.dataset.filterNeedsObject': 'Pick a base object to add filter conditions.', }; export default STUDIO_EN; diff --git a/ui/packages/i18n/src/namespaces/studio/zh.ts b/ui/packages/i18n/src/namespaces/studio/zh.ts index 227a097d5f6..d2a2894511b 100644 --- a/ui/packages/i18n/src/namespaces/studio/zh.ts +++ b/ui/packages/i18n/src/namespaces/studio/zh.ts @@ -410,6 +410,556 @@ const STUDIO_ZH: Record = { // AI assistant entry points 'designer.canvas.askAi': '问 AI', 'designer.canvas.askAiGenerate': '用 AI 生成字段', + // engine.inspector — the page block, action, dataset, flow node, app + // navigation and dashboard widget inspectors (#25922 slice S2). + 'engine.inspector.widget.kind': '组件', + 'engine.inspector.widget.close': '关闭组件', + 'engine.inspector.widget.title': '标题', + 'engine.inspector.widget.type': '类型', + 'engine.inspector.widget.object': '数据源(对象)', + 'engine.inspector.widget.valueField': '取值字段', + 'engine.inspector.widget.categoryField': '分组字段', + 'engine.inspector.widget.aggregate': '聚合方式', + 'engine.inspector.widget.color': '颜色', + 'engine.inspector.widget.width': '宽度', + 'engine.inspector.widget.height': '高度', + 'engine.inspector.widget.remove': '删除组件', + // Dataset binding (ADR-0021) — governed cross-object semantic layer. + 'engine.inspector.widget.datasetSection': '数据集绑定', + 'engine.inspector.widget.dataset': '数据集', + 'engine.inspector.widget.datasetPlaceholder': '例如:sales_pipeline', + 'engine.inspector.widget.datasetHint': + '绑定一个受治理的数据集。设置后优先于上方的内联对象查询,组件改为按数据集渲染(口径一致、可跨对象、强制 RLS)。', + 'engine.inspector.widget.dimensions': '维度', + 'engine.inspector.widget.dimensionsPlaceholder': '例如:stage, region(逗号分隔)', + 'engine.inspector.widget.dimensionsHint': '用于分组的数据集维度名。留空则渲染为单值 KPI。', + 'engine.inspector.widget.values': '值(度量)', + 'engine.inspector.widget.valuesPlaceholder': '例如:revenue, deal_count(逗号分隔)', + 'engine.inspector.widget.valuesHint': '要展示的数据集度量名。', + // Dashboard filter bindings (framework#2501) + 'engine.inspector.widget.filterBindingsSection': '仪表盘过滤器绑定', + 'engine.inspector.widget.filterBindingsHint': + '把每个仪表盘级过滤器映射到本组件自己的字段;取消勾选「应用」可让本组件不受该过滤器影响。留空表示使用过滤器自身的字段。', + 'engine.inspector.widget.filterBindingApply': '应用', + 'engine.inspector.widget.filterBindingDefault': '默认({field})', + 'engine.inspector.widget.filterBindingField': '{filter} 绑定的字段', + 'engine.inspector.widget.filterBindingReset': '恢复默认', + 'engine.inspector.widget.type.metric': 'KPI 指标', + 'engine.inspector.widget.type.bar': '柱状图', + 'engine.inspector.widget.type.horizontalBar': '条形图', + 'engine.inspector.widget.type.line': '折线图', + 'engine.inspector.widget.type.area': '面积图', + 'engine.inspector.widget.type.pie': '饼图', + 'engine.inspector.widget.type.donut': '环形图', + 'engine.inspector.widget.type.funnel': '漏斗图', + 'engine.inspector.widget.type.table': '表格', + 'engine.inspector.widget.type.pivot': '透视表', + 'engine.inspector.widget.untitledN': '组件 {n}', + 'engine.inspector.widget.unsupportedSelection': '不支持的选择类型:{kind}', + 'engine.inspector.widget.removed': '所选组件已从草稿中移除。', + // Flow node inspector + 'engine.inspector.flowNode.kind': '节点', + 'engine.inspector.flowNode.close': '关闭节点', + 'engine.inspector.flowNode.id': 'ID', + 'engine.inspector.flowNode.idRequired': '未重命名:节点必须有 ID。', + 'engine.inspector.flowNode.idTaken': '未重命名:此流程中已有另一个节点的 ID 是“{id}”。', + 'engine.inspector.flowNode.idEdgeNamed': '未重命名:仍有连线指向“{id}”,而该节点已不在流程中。请先删除或重新连接那条连线。', + 'engine.inspector.flowNode.idRefsUnparsed': '未重命名:以下表达式引用了“{id}”,但无法解析,重命名无法同步更新它们:{refs}。请先修正。', + 'engine.inspector.flowNode.idRefsAmbiguous': '未重命名:“{name}”同时也是此流程中某个变量的名称,以下表达式可能引用其中任何一个,无法同步更新:{refs}。请先重命名该变量,或手动修改这些表达式。', + 'engine.inspector.flowNode.removeRefused': '未删除:以下位置仍引用“{id}”,删除后将指向不存在的节点:{refs}。请先修改或删除它们。', + 'engine.inspector.flowNode.label': '标签', + 'engine.inspector.flowNode.type': '节点类型', + 'engine.inspector.flowNode.configuration': '配置', + 'engine.inspector.flowNode.config': '配置(JSON)', + 'engine.inspector.flowNode.advanced': '高级(JSON)', + 'engine.inspector.flowNode.advancedHint': '上方表单未覆盖的可选自定义键 —— 大多数流程无需填写。', + 'engine.inspector.flowNode.inactiveRetained': '已保留,未生效 —— 控制字段处于关闭。该值仍然存储,直到你清除它。', + 'engine.inspector.flowNode.inactiveRetainedOrphan': '已保留,未生效 —— 没有任何开关能启用此字段。该值仍然存储,以免静默丢失。', + 'engine.inspector.flowNode.inactiveRetainedClear': '清除值', + 'engine.inspector.flowNode.cadenceLabel': '调度', + 'engine.inspector.flowNode.cadenceEveryMs': '每 {n} 毫秒', + 'engine.inspector.flowNode.cadenceEverySeconds': '每 {n} 秒', + 'engine.inspector.flowNode.cadenceEveryMinutes': '每 {n} 分钟', + 'engine.inspector.flowNode.cadenceEveryHours': '每 {n} 小时', + 'engine.inspector.flowNode.cadenceEveryDays': '每 {n} 天', + 'engine.inspector.flowNode.cadenceOnce': '仅一次,于 {at}', + 'engine.inspector.flowNode.cadenceReadOnly': '此流程按固定间隔或在指定时刻运行,而非按 cron。设计器只编辑 cron 调度,因此这里按存储原样显示此调度,且保持不变。', + 'engine.inspector.flowNode.noConfig': '此节点类型无需配置。', + 'engine.inspector.flowNode.nestedIdHint': '容器区域内的节点 ID 在此只读 —— 请在容器的高级 JSON 中重命名。', + 'engine.inspector.flowNode.nestedLocation': '嵌套节点位置', + 'engine.inspector.flowNode.advancedNotObject': '必须是 JSON 对象', + 'engine.inspector.flowNode.kv.add': '添加条目', + 'engine.inspector.flowNode.kv.key': '键', + 'engine.inspector.flowNode.kv.value': '值', + 'engine.inspector.flowNode.kv.remove': '删除条目', + 'engine.inspector.flowNode.kv.empty': '暂无条目。', + 'engine.inspector.flowNode.kv.asExpression': '写成 CEL 表达式', + 'engine.inspector.flowNode.kv.expressionPlaceholder': 'CEL 表达式,例如 joinNonEmpty(…)', + 'engine.inspector.flowNode.list.add': '添加项', + 'engine.inspector.flowNode.list.item': '值', + 'engine.inspector.flowNode.list.remove': '删除项', + 'engine.inspector.flowNode.list.empty': '暂无项。', + 'engine.inspector.flowNode.branch.add': '添加分支', + 'engine.inspector.flowNode.branch.fromEdges': '此决策按其连线上的条件路由。每张卡片是一条连线,请在连线上编辑它的条件。', + 'engine.inspector.flowNode.branch.openEdge': '编辑连线', + 'engine.inspector.flowNode.branch.noCondition': '无条件', + 'engine.inspector.flowNode.remove': '删除节点', + // App nav inspector + 'engine.inspector.appNav.kind': '导航项', + 'engine.inspector.appNav.close': '关闭导航项', + 'engine.inspector.appNav.label': '名称', + 'engine.inspector.appNav.icon': '图标', + 'engine.inspector.appNav.remove': '删除导航项', + 'engine.inspector.appNav.typeField': '类型', + 'engine.inspector.appNav.type.object': '对象', + 'engine.inspector.appNav.type.page': '页面', + 'engine.inspector.appNav.type.dashboard': '仪表盘', + 'engine.inspector.appNav.type.report': '报表', + 'engine.inspector.appNav.type.url': '链接(URL)', + 'engine.inspector.appNav.type.group': '分组', + 'engine.inspector.appNav.object': '对象', + 'engine.inspector.appNav.targetMode': '落点', + 'engine.inspector.appNav.mode.default': '默认视图(工作台)', + 'engine.inspector.appNav.mode.view': '具名视图', + 'engine.inspector.appNav.mode.record': '记录深链', + 'engine.inspector.appNav.mode.filters': '参数化切面(/data)', + 'engine.inspector.appNav.view': '视图', + 'engine.inspector.appNav.recordId': '记录 ID', + 'engine.inspector.appNav.recordIdHint': '支持 {current_user_id} 与 {current_org_id} 模板变量。', + 'engine.inspector.appNav.recordMode': '打开方式', + 'engine.inspector.appNav.recordModeView': '查看', + 'engine.inspector.appNav.recordModeEdit': '编辑', + 'engine.inspector.appNav.filters': 'URL 过滤条件', + 'engine.inspector.appNav.filtersField': '字段', + 'engine.inspector.appNav.filtersValue': '值', + 'engine.inspector.appNav.filtersAdd': '添加条件', + 'engine.inspector.appNav.filtersRemove': '移除条件', + 'engine.inspector.appNav.filtersHint': '一次性切面由 URL 承载——当它值得沉淀复用时,请改建具名视图。', + 'engine.inspector.appNav.url': '链接地址', + 'engine.inspector.appNav.urlTarget': '打开位置', + 'engine.inspector.appNav.urlTargetSelf': '当前标签页', + 'engine.inspector.appNav.urlTargetBlank': '新标签页', + 'engine.inspector.appNav.preview': '运行期落点', + // objectui#11790 — the Studio nav editor's Type choice and pickers. + 'engine.inspector.appNav.type.action': '动作', + 'engine.inspector.appNav.type.component': '组件', + 'engine.inspector.appNav.type.doc': '文档', + 'engine.inspector.appNav.type.separator': '分隔线', + 'engine.inspector.appNav.choose': '— 请选择 —', + 'engine.inspector.appNav.noTargets': '还没有可链接的此类型条目。', + 'engine.inspector.appNav.unboundHint': '选定目标之前,该导航项保留在此处,但不会写入保存的导航。', + 'engine.inspector.appNav.actionHint': '只有未绑定到对象的动作才能从菜单运行。', + 'engine.inspector.appNav.docPage': '文档页', + 'engine.inspector.appNav.book': '文档集', + 'engine.inspector.appNav.docHint': '选择文档页、文档集,或两者都选。', + 'engine.inspector.appNav.groupHint': '分组是菜单中带标题的分区。此编辑器不支持把导航项嵌套到分组下。', + 'engine.inspector.appNav.separatorHint': '分隔线只是一条分隔:没有名称,也不打开任何内容。', + // Page block inspector + 'engine.inspector.pageBlock.kind': '区块', + 'engine.inspector.pageBlock.close': '关闭区块', + 'engine.inspector.pageBlock.type': '类型', + 'engine.inspector.pageBlock.id': 'ID', + 'engine.inspector.pageBlock.className': '类名', + 'engine.inspector.pageBlock.visibleWhen': '显示条件(CEL)', + 'engine.inspector.pageBlock.properties': '属性', + 'engine.inspector.pageBlock.advanced': '高级属性', + 'engine.inspector.pageBlock.remove': '删除区块', + 'engine.inspector.pageBlock.outlineLabel': '区块', + // 面板自身列表/JSON 编辑器的 chrome(#3963)—— 不是 block-config 表数据。 + 'engine.inspector.pageBlock.list.add': '添加', + 'engine.inspector.pageBlock.list.remove': '删除', + 'engine.inspector.pageBlock.list.removeItem': '删除项', + 'engine.inspector.pageBlock.objectPlaceholder': 'snake_case 对象名', + 'engine.inspector.pageBlock.fieldPlaceholder': '字段名', + // 页面区块检查器 —— curated 属性 label(#3913)。 + // 键形状与 en 侧一一对应,由 `previews/__tests__/block-config-i18n.test.ts` + // 按 BLOCK_CONFIG 的结构重新推导并断言两语齐备。 + 'engine.inspector.pageBlock.field.object-grid.objectName': '对象', + 'engine.inspector.pageBlock.field.object-grid.columns': '列', + 'engine.inspector.pageBlock.field.object-grid.pageSize': '每页条数', + 'engine.inspector.pageBlock.field.object-form.objectName': '对象', + 'engine.inspector.pageBlock.field.object-form.mode': '模式', + 'engine.inspector.pageBlock.option.mode.create': '新建', + 'engine.inspector.pageBlock.option.mode.edit': '编辑', + 'engine.inspector.pageBlock.option.mode.view': '查看', + 'engine.inspector.pageBlock.field.object-form.formType': '表单类型', + 'engine.inspector.pageBlock.option.formType.simple': '简单', + 'engine.inspector.pageBlock.option.formType.tabbed': '标签页', + 'engine.inspector.pageBlock.option.formType.wizard': '向导', + 'engine.inspector.pageBlock.option.formType.split': '分栏', + 'engine.inspector.pageBlock.option.formType.drawer': '抽屉', + 'engine.inspector.pageBlock.option.formType.modal': '弹窗', + 'engine.inspector.pageBlock.field.object-form.layout': '布局', + 'engine.inspector.pageBlock.option.layout.vertical': '纵向', + 'engine.inspector.pageBlock.option.layout.horizontal': '横向', + 'engine.inspector.pageBlock.field.object-form.columns': '字段列数(1–4)', + 'engine.inspector.pageBlock.field.object-form.fields': '字段', + 'engine.inspector.pageBlock.field.object-form.title': '标题', + 'engine.inspector.pageBlock.field.object-form.description': '描述', + 'engine.inspector.pageBlock.field.object-metric.objectName': '对象', + 'engine.inspector.pageBlock.field.object-metric.label': '标签', + 'engine.inspector.pageBlock.field.object-metric.description': '描述', + 'engine.inspector.pageBlock.field.object-metric.icon': '图标', + 'engine.inspector.pageBlock.placeholder.object-metric.icon': 'lucide 图标名', + 'engine.inspector.pageBlock.field.object-metric.colorVariant': '颜色', + 'engine.inspector.pageBlock.option.colorVariant.default': '默认', + 'engine.inspector.pageBlock.option.colorVariant.blue': '蓝色', + 'engine.inspector.pageBlock.option.colorVariant.teal': '青色', + 'engine.inspector.pageBlock.option.colorVariant.orange': '橙色', + 'engine.inspector.pageBlock.option.colorVariant.purple': '紫色', + 'engine.inspector.pageBlock.option.colorVariant.success': '成功', + 'engine.inspector.pageBlock.option.colorVariant.warning': '警告', + 'engine.inspector.pageBlock.option.colorVariant.danger': '危险', + 'engine.inspector.pageBlock.field.object-metric.format': '格式', + 'engine.inspector.pageBlock.option.format.number': '数字', + 'engine.inspector.pageBlock.option.format.currency': '货币', + 'engine.inspector.pageBlock.option.format.percent': '百分比', + 'engine.inspector.pageBlock.field.object-metric.prefix': '前缀', + 'engine.inspector.pageBlock.field.object-metric.suffix': '后缀', + 'engine.inspector.pageBlock.field.object-kanban.objectName': '对象', + 'engine.inspector.pageBlock.field.object-kanban.groupBy': '分组字段', + 'engine.inspector.pageBlock.field.object-kanban.titleField': '标题字段', + 'engine.inspector.pageBlock.field.object-kanban.cardFields': '卡片字段', + 'engine.inspector.pageBlock.field.object-kanban.limit': '条数上限', + 'engine.inspector.pageBlock.field.grid.columns': '列数', + 'engine.inspector.pageBlock.field.grid.gap': '间距', + 'engine.inspector.pageBlock.field.element:text.content': '内容', + 'engine.inspector.pageBlock.placeholder.element:text.content': '文本…', + 'engine.inspector.pageBlock.field.element:text.variant': '样式', + 'engine.inspector.pageBlock.option.variant.h1': '标题 1', + 'engine.inspector.pageBlock.option.variant.h2': '标题 2', + 'engine.inspector.pageBlock.option.variant.h3': '标题 3', + 'engine.inspector.pageBlock.option.variant.h4': '标题 4', + 'engine.inspector.pageBlock.option.variant.h5': '标题 5', + 'engine.inspector.pageBlock.option.variant.h6': '标题 6', + 'engine.inspector.pageBlock.option.variant.body': '正文', + 'engine.inspector.pageBlock.option.variant.caption': '说明文字', + 'engine.inspector.pageBlock.option.variant.overline': '眉题', + 'engine.inspector.pageBlock.field.element:text.align': '对齐', + 'engine.inspector.pageBlock.option.align.left': '左对齐', + 'engine.inspector.pageBlock.option.align.center': '居中', + 'engine.inspector.pageBlock.option.align.right': '右对齐', + 'engine.inspector.pageBlock.field.element:image.src': '图片地址', + 'engine.inspector.pageBlock.field.element:image.alt': '替代文本', + 'engine.inspector.pageBlock.field.element:image.fit': '填充方式', + 'engine.inspector.pageBlock.option.fit.cover': '覆盖', + 'engine.inspector.pageBlock.option.fit.contain': '包含', + 'engine.inspector.pageBlock.option.fit.fill': '拉伸', + 'engine.inspector.pageBlock.field.element:definition-list.items': '条目', + 'engine.inspector.pageBlock.add.element:definition-list.items': '添加条目', + 'engine.inspector.pageBlock.field.element:definition-list.items.term': '术语', + 'engine.inspector.pageBlock.field.element:definition-list.items.description': '描述', + 'engine.inspector.pageBlock.field.element:definition-list.columns': '列数(1 或 2)', + 'engine.inspector.pageBlock.field.element:definition-list.inline': '行内(术语 · 描述)', + 'engine.inspector.pageBlock.field.element:repeater.object': '对象', + 'engine.inspector.pageBlock.field.element:repeater.titleField': '标题字段', + 'engine.inspector.pageBlock.field.element:repeater.fields': '字段', + 'engine.inspector.pageBlock.field.element:repeater.limit': '条数上限', + 'engine.inspector.pageBlock.field.element:repeater.emptyText': '空数据提示', + 'engine.inspector.pageBlock.field.element:repeater.divided': '行间分隔线', + 'engine.inspector.pageBlock.field.element:number.object': '对象', + 'engine.inspector.pageBlock.field.element:number.field': '字段', + 'engine.inspector.pageBlock.field.element:number.aggregate': '聚合方式', + 'engine.inspector.pageBlock.option.aggregate.count': '计数', + 'engine.inspector.pageBlock.option.aggregate.sum': '求和', + 'engine.inspector.pageBlock.option.aggregate.avg': '平均值', + 'engine.inspector.pageBlock.option.aggregate.min': '最小值', + 'engine.inspector.pageBlock.option.aggregate.max': '最大值', + 'engine.inspector.pageBlock.field.element:number.format': '格式', + 'engine.inspector.pageBlock.field.element:number.prefix': '前缀', + 'engine.inspector.pageBlock.field.element:number.suffix': '后缀', + 'engine.inspector.pageBlock.field.element:button.label': '按钮文字', + 'engine.inspector.pageBlock.field.element:button.variant': '样式', + 'engine.inspector.pageBlock.option.variant.primary': '主要', + 'engine.inspector.pageBlock.option.variant.secondary': '次要', + 'engine.inspector.pageBlock.option.variant.danger': '危险', + 'engine.inspector.pageBlock.option.variant.ghost': '幽灵', + 'engine.inspector.pageBlock.option.variant.link': '链接', + 'engine.inspector.pageBlock.field.element:button.size': '尺寸', + 'engine.inspector.pageBlock.option.size.small': '小', + 'engine.inspector.pageBlock.option.size.medium': '中', + 'engine.inspector.pageBlock.option.size.large': '大', + 'engine.inspector.pageBlock.field.element:button.icon': '图标', + 'engine.inspector.pageBlock.placeholder.element:button.icon': 'lucide 图标名', + 'engine.inspector.pageBlock.field.element:button.action': '动作', + 'engine.inspector.pageBlock.field.page:header.title': '标题', + 'engine.inspector.pageBlock.field.page:header.subtitle': '副标题', + // `…field.page:header.icon` and its placeholder retired with the spec key — + // see the matching note in the `en` table above. Removed from BOTH tables in + // the same edit so the two key sets stay identical. `…field.page:header.breadcrumb` + // likewise left both tables with its toggle (objectui#11173). + 'engine.inspector.pageBlock.field.page:card.title': '标题', + 'engine.inspector.pageBlock.field.page:card.bordered': '显示边框', + 'engine.inspector.pageBlock.field.page:tabs.items': '标签页', + 'engine.inspector.pageBlock.add.page:tabs.items': '添加标签页', + 'engine.inspector.pageBlock.field.page:tabs.items.value': '值', + 'engine.inspector.pageBlock.field.page:tabs.items.label': '标签', + // `…field.page:accordion.title` / `…items.value` retired with their designer + // fields — see the matching note in the `en` table above. Removed from BOTH + // tables in the same edit so the two key sets stay identical. + 'engine.inspector.pageBlock.field.page:accordion.items': '分区', + 'engine.inspector.pageBlock.add.page:accordion.items': '添加分区', + 'engine.inspector.pageBlock.field.page:accordion.items.label': '标签', + 'engine.inspector.pageBlock.field.record:related_list.objectName': '对象', + 'engine.inspector.pageBlock.field.record:related_list.relationshipField': '关联字段', + 'engine.inspector.pageBlock.field.record:related_list.title': '标题', + 'engine.inspector.pageBlock.field.record:related_list.limit': '条数上限', + 'engine.inspector.pageBlock.field.record:highlights.fields': '字段', + 'engine.inspector.pageBlock.field.record:details.sections': '分区', + 'engine.inspector.pageBlock.add.record:details.sections': '添加分区', + 'engine.inspector.pageBlock.field.record:details.sections.name': '名称(i18n 键)', + 'engine.inspector.pageBlock.placeholder.record:details.sections.name': 'snake_case,例如:contact_info', + 'engine.inspector.pageBlock.field.record:details.sections.label': '标签', + 'engine.inspector.pageBlock.field.record:details.sections.columns': '列数', + 'engine.inspector.pageBlock.field.record:details.sections.fields': '字段', + 'engine.inspector.pageBlock.field.record:alert.severity': '严重度', + 'engine.inspector.pageBlock.option.severity.info': '信息', + 'engine.inspector.pageBlock.option.severity.warning': '警告', + 'engine.inspector.pageBlock.option.severity.error': '错误', + 'engine.inspector.pageBlock.option.severity.success': '成功', + 'engine.inspector.pageBlock.field.record:alert.title': '标题', + 'engine.inspector.pageBlock.field.record:alert.body': '正文', + 'engine.inspector.pageBlock.field.record:alert.icon': '图标', + 'engine.inspector.pageBlock.placeholder.record:alert.icon': 'lucide 图标名', + 'engine.inspector.pageBlock.field.record:alert.dismissible': '可关闭', + 'engine.inspector.pageBlock.field.record:path.statusField': '状态字段', + 'engine.inspector.pageBlock.field.record:path.stages': '阶段', + 'engine.inspector.pageBlock.add.record:path.stages': '添加阶段', + 'engine.inspector.pageBlock.field.record:path.stages.value': '值', + 'engine.inspector.pageBlock.field.record:path.stages.label': '标签', + 'engine.inspector.pageBlock.field.record:quick_actions.actionNames': '动作名称', + 'engine.inspector.pageBlock.placeholder.record:quick_actions.actionNames': '动作名称', + 'engine.inspector.pageBlock.field.record:quick_actions.location': '位置', + 'engine.inspector.pageBlock.option.location.record_header': '记录头部', + 'engine.inspector.pageBlock.option.location.record_more': '记录更多菜单', + 'engine.inspector.pageBlock.option.location.record_section': '记录分区', + 'engine.inspector.pageBlock.option.location.record_related': '记录关联列表', + 'engine.inspector.pageBlock.option.location.list_toolbar': '列表工具栏', + 'engine.inspector.pageBlock.option.location.list_item': '列表行', + // `…option.location.global_nav` retired with the location itself — see the + // matching note in `./en.ts`. Removed from BOTH tables in the same + // edit so the two key sets stay identical. + // Action default (home) inspector + 'engine.inspector.action.kind': '动作', + 'engine.inspector.action.close': '关闭动作', + // Action default (home) inspector (objectui#10586). Wording follows the + // catalogue's own rows: pageBlock's variant and location labels, the + // `engine.fieldType.*` names, `designer.field.*` (显示名 / 占位符 / 必填). + 'engine.inspector.action.type.script': '脚本 —— 运行表达式 / 沙箱 JS', + 'engine.inspector.action.type.api': 'API —— 调用端点', + 'engine.inspector.action.type.flow': '流程 —— 调用流程', + 'engine.inspector.action.type.modal': '弹窗 —— 打开弹窗 / 页面', + 'engine.inspector.action.type.form': '表单 —— 打开表单视图', + 'engine.inspector.action.type.url': 'URL —— 跳转到链接', + 'engine.inspector.action.variant.primary': '主要', + 'engine.inspector.action.variant.secondary': '次要', + 'engine.inspector.action.variant.danger': '危险', + 'engine.inspector.action.variant.ghost': '幽灵', + 'engine.inspector.action.variant.link': '链接', + 'engine.inspector.action.component.button': '按钮', + 'engine.inspector.action.component.icon': '仅图标', + 'engine.inspector.action.component.menu': '菜单项', + 'engine.inspector.action.component.group': '按钮组', + 'engine.inspector.action.mode.create': '新建', + 'engine.inspector.action.mode.edit': '编辑', + 'engine.inspector.action.mode.delete': '删除', + 'engine.inspector.action.mode.custom': '自定义', + 'engine.inspector.action.bodyLang.expression': '表达式(L1)', + 'engine.inspector.action.bodyLang.js': '沙箱 JS(L2)', + 'engine.inspector.action.paramType.text': '单行文本', + 'engine.inspector.action.paramType.textarea': '多行文本', + 'engine.inspector.action.paramType.number': '数字', + 'engine.inspector.action.paramType.boolean': '复选框', + 'engine.inspector.action.paramType.select': '下拉选择', + 'engine.inspector.action.paramType.date': '日期', + 'engine.inspector.action.paramType.datetime': '日期时间', + 'engine.inspector.action.paramType.lookup': '查找关系', + 'engine.inspector.action.location.record_header': '记录头部', + 'engine.inspector.action.location.record_more': '记录 · 更多菜单', + 'engine.inspector.action.location.record_section': '记录 · 分区', + 'engine.inspector.action.location.record_related': '记录 · 关联列表', + 'engine.inspector.action.location.list_toolbar': '列表工具栏', + 'engine.inspector.action.location.list_item': '列表 · 行', + 'engine.inspector.action.target.url.label': 'URL 地址', + 'engine.inspector.action.target.url.placeholder': 'https://… 或 /path?x=${param.x}', + 'engine.inspector.action.target.url.hint': '支持 ${param.x} 与 ${ctx.x} 插值。', + 'engine.inspector.action.target.flow.label': '流程名称', + 'engine.inspector.action.target.flow.placeholder': 'snake_case 流程名', + 'engine.inspector.action.target.flow.hint': '点击时调用的流程。', + 'engine.inspector.action.target.modal.label': '弹窗 / 页面名称', + 'engine.inspector.action.target.modal.placeholder': 'snake_case 页面名', + 'engine.inspector.action.target.modal.hint': '要打开的弹窗或页面。', + 'engine.inspector.action.target.form.label': '表单视图名称', + 'engine.inspector.action.target.form.hint': '打开 /console/forms/。', + 'engine.inspector.action.target.api.label': 'API 端点', + 'engine.inspector.action.target.api.hint': '使用下方请求体调用的端点。', + 'engine.inspector.action.scriptOp.run': '运行脚本 —— 沙箱 JS / 表达式主体', + 'engine.inspector.action.scriptOp.update': '更新此记录的字段 —— 无需代码', + 'engine.inspector.action.objectPlaceholder': 'snake_case 对象名', + 'engine.inspector.action.objectNone': '— 无(全局)—', + 'engine.inspector.action.options': '选项', + 'engine.inspector.action.optionsEmpty': '还没有选项 —— 没有选项的下拉选择会在对话框中打开一个空的选择器。', + 'engine.inspector.action.optionLabel': '显示名', + 'engine.inspector.action.optionValue': '值', + 'engine.inspector.action.removeOption': '移除选项 {index}', + 'engine.inspector.action.addOption': '添加选项', + 'engine.inspector.action.basics': '基本信息', + 'engine.inspector.action.label': '显示名', + 'engine.inspector.action.labelPlaceholder': '向用户显示的按钮文字', + 'engine.inspector.action.name': '名称', + 'engine.inspector.action.namePlaceholder': 'snake_case 标识符', + 'engine.inspector.action.object': '对象', + 'engine.inspector.action.objectBoundHint': '绑定动作 —— 按下方的放置位置出现在此对象的视图中。', + 'engine.inspector.action.objectGlobalHint': '留空 = 全局动作 —— 须被页面的快捷动作、全局导航、流程或 AI 引用后才会出现。', + 'engine.inspector.action.icon': '图标', + 'engine.inspector.action.variant': '样式', + 'engine.inspector.action.behavior': '行为', + 'engine.inspector.action.behaviorHint': '动作被触发时会发生什么。', + 'engine.inspector.action.type': '类型', + 'engine.inspector.action.pinnedScript': '已固定为 {script}:字段写入在平台动作路由上执行,而这正是该类型自己的路由。与它并存时,其他任何类型都会被拒绝。', + 'engine.inspector.action.whatItDoes': '作用', + 'engine.inspector.action.fieldValues': '字段值', + 'engine.inspector.action.fieldValuesEmpty': '还没有字段值 —— 添加一个,或在下方把该值作为输入项收集。', + 'engine.inspector.action.fieldValueField': '字段', + 'engine.inspector.action.fieldValueValue': '值', + 'engine.inspector.action.removeFieldValue': '移除字段值', + 'engine.inspector.action.addFieldValue': '添加字段值', + 'engine.inspector.action.updateHint': '以调用者身份写入当前记录 —— 对象权限、钩子与校验会像用户编辑时一样触发。下方收集的同名输入项会覆盖此处的固定值。', + 'engine.inspector.action.scriptLanguage': '脚本语言', + 'engine.inspector.action.scriptBody': '脚本主体', + 'engine.inspector.action.scriptBodyHint': '在沙箱中以 {signature} 的形式运行。', + 'engine.inspector.action.method': '请求方法', + 'engine.inspector.action.inputs': '输入项', + 'engine.inspector.action.inputsHint': '在动作运行前通过对话框向用户收集。', + 'engine.inspector.action.inputsEmpty': '没有输入项 —— 点击后立即运行动作。', + 'engine.inspector.action.removeInput': '移除输入项', + 'engine.inspector.action.bindToField': '绑定到字段', + 'engine.inspector.action.paramName': '名称', + 'engine.inspector.action.paramNamePlaceholder': '请求体键名', + 'engine.inspector.action.paramLabel': '显示名', + 'engine.inspector.action.paramTypeLabel': '类型', + 'engine.inspector.action.paramPlaceholder': '占位符', + 'engine.inspector.action.paramRequired': '必填', + 'engine.inspector.action.paramPrefill': '从行预填', + 'engine.inspector.action.addInput': '添加输入项', + 'engine.inspector.action.placement': '放置位置', + 'engine.inspector.action.placementHint': '此动作在界面中出现的位置。', + 'engine.inspector.action.noPlacement': '未选择放置位置 —— 此动作不会出现在任何记录或列表界面上。请在上方勾选一个放置位置,或通过视图的批量动作放置它。', + 'engine.inspector.action.capabilityGate': '受权限 {capabilities} 控制 —— 对于不具备{whom}的任何人,上方勾选的每个放置位置都会隐藏此动作。这不是置灰,也不是错误:按钮只是不出现,而且无论如何服务器都会以 403 拒绝调用。', + 'engine.inspector.action.capabilityGateAll': '全部这些权限', + 'engine.inspector.action.capabilityGateOne': '该权限', + 'engine.inspector.action.capabilityGateSelf': '当前会话不具备 {capabilities},因此该按钮对你同样隐藏 —— 通过权限集授予后,即可在应用中看到它。', + 'engine.inspector.action.component': '组件', + 'engine.inspector.action.feedback': '反馈', + 'engine.inspector.action.feedbackHint': '确认提示与运行后的消息。', + 'engine.inspector.action.confirmText': '确认提示', + 'engine.inspector.action.confirmTextPlaceholder': '运行前询问(留空则跳过)', + 'engine.inspector.action.successMessage': '成功消息', + 'engine.inspector.action.errorMessage': '错误消息', + 'engine.inspector.action.mode': '模式', + 'engine.inspector.action.refreshAfter': '完成后刷新视图', + 'engine.inspector.action.undoable': '提供撤销', + 'engine.inspector.action.conditions': '条件', + 'engine.inspector.action.conditionsHint': '基于 record / user / ctx 的无代码判断条件(编译为 CEL)。', + 'engine.inspector.action.visibleWhen': '可见条件', + 'engine.inspector.action.disabledWhen': '禁用条件', + 'engine.inspector.action.ai': 'AI 开放', + 'engine.inspector.action.aiHint': '可选:将此动作作为可调用工具开放给 AI 智能体。', + 'engine.inspector.action.aiExposed': '开放给 AI 智能体', + 'engine.inspector.action.aiDescription': '工具描述(必填,≥40 个字符)', + 'engine.inspector.action.aiDescriptionPlaceholder': '智能体应在何时、出于何种目的调用此动作…', + 'engine.inspector.action.aiDescriptionTooShort': '开放期间必须提供至少 40 个字符的描述。', + 'engine.inspector.action.moreFieldsHint': '高级 / 不常用的属性。', + // Dataset default (home) inspector (objectui#10586). zh has one plural form, + // so each `…One` / `…Other` pair carries the same row. + 'engine.inspector.dataset.kind': '数据集', + 'engine.inspector.dataset.aggregate.count': '计数', + 'engine.inspector.dataset.aggregate.sum': '求和', + 'engine.inspector.dataset.aggregate.avg': '平均值', + 'engine.inspector.dataset.aggregate.min': '最小值', + 'engine.inspector.dataset.aggregate.max': '最大值', + 'engine.inspector.dataset.aggregate.count_distinct': '去重计数', + 'engine.inspector.dataset.dimType.string': '字符串', + 'engine.inspector.dataset.dimType.number': '数字', + 'engine.inspector.dataset.dimType.date': '日期', + 'engine.inspector.dataset.dimType.boolean': '布尔值', + 'engine.inspector.dataset.dimType.lookup': '查找关系', + 'engine.inspector.dataset.granularity.none': '— 无 —', + 'engine.inspector.dataset.granularity.day': '日', + 'engine.inspector.dataset.granularity.week': '周', + 'engine.inspector.dataset.granularity.month': '月', + 'engine.inspector.dataset.granularity.quarter': '季度', + 'engine.inspector.dataset.granularity.year': '年', + 'engine.inspector.dataset.derivedOp.ratio': '比率(a ÷ b)', + 'engine.inspector.dataset.derivedOp.sum': '求和(a + b)', + 'engine.inspector.dataset.derivedOp.difference': '差值(a − b)', + 'engine.inspector.dataset.derivedOp.product': '乘积(a × b)', + 'engine.inspector.dataset.formatKind.raw': '原始数值', + 'engine.inspector.dataset.formatKind.number': '数字 —— 1,234.5', + 'engine.inspector.dataset.formatKind.currency': '货币 —— $1,234.50', + 'engine.inspector.dataset.formatKind.percent': '百分比 —— 12.3%', + 'engine.inspector.dataset.usage.bound': '已被 {reports} · {dashboards} 绑定 —— 修改会影响它们。', + 'engine.inspector.dataset.usage.reportsOne': '{count} 个报表', + 'engine.inspector.dataset.usage.reportsOther': '{count} 个报表', + 'engine.inspector.dataset.usage.dashboardsOne': '{count} 个仪表盘', + 'engine.inspector.dataset.usage.dashboardsOther': '{count} 个仪表盘', + 'engine.inspector.dataset.usage.unbound': '尚未被任何报表或仪表盘绑定。', + 'engine.inspector.dataset.name': '名称', + 'engine.inspector.dataset.namePlaceholder': 'snake_case 标识符', + 'engine.inspector.dataset.label': '显示名', + 'engine.inspector.dataset.description': '描述', + 'engine.inspector.dataset.baseObject': '基础对象', + 'engine.inspector.dataset.baseObjectPlaceholder': '选择对象…', + 'engine.inspector.dataset.searchObjects': '搜索对象…', + 'engine.inspector.dataset.baseObjectChangeHint': '更改基础对象会清空其维度、字段度量、关联与筛选;行计数会保留。', + 'engine.inspector.dataset.includedRelationships': '包含的关联', + 'engine.inspector.dataset.noJoins': '暂无关联。添加一个关联({object} 上的查找字段),即可使用 {path} 形式的维度 / 度量。', + 'engine.inspector.dataset.baseObjectFallback': '基础对象', + 'engine.inspector.dataset.includedRelationship': '包含的关联', + 'engine.inspector.dataset.relationshipPlaceholder': '选择关联…', + 'engine.inspector.dataset.searchRelationships': '搜索关联…', + 'engine.inspector.dataset.removeRelationship': '移除关联', + 'engine.inspector.dataset.scopeFilter': '范围筛选', + 'engine.inspector.dataset.scopeFilterHelp': '固有范围,以 AND 并入每一次查询(例如排除已软删除的记录)。', + 'engine.inspector.dataset.dimensions': '维度', + 'engine.inspector.dataset.addDimension': '添加维度', + 'engine.inspector.dataset.dimensionN': '维度 {index}', + 'engine.inspector.dataset.removeDimension': '移除维度', + 'engine.inspector.dataset.dimensionNamePlaceholder': '例如:region', + 'engine.inspector.dataset.field': '字段', + 'engine.inspector.dataset.dimensionFieldPlaceholder': '字段 或 关联.字段', + 'engine.inspector.dataset.type': '类型', + 'engine.inspector.dataset.labelOptional': '显示名(可选)', + 'engine.inspector.dataset.displayLabel': '显示名', + 'engine.inspector.dataset.dateBucket': '日期分桶', + 'engine.inspector.dataset.measures': '度量', + 'engine.inspector.dataset.addMeasure': '添加度量', + 'engine.inspector.dataset.measureN': '度量 {index}', + 'engine.inspector.dataset.removeMeasure': '移除度量', + 'engine.inspector.dataset.measureNamePlaceholder': '例如:revenue', + 'engine.inspector.dataset.aggregate': '聚合方式', + 'engine.inspector.dataset.measureFieldPlaceholder': '字段(count 时可选)', + 'engine.inspector.dataset.measureFilter': '筛选(度量级)', + 'engine.inspector.dataset.measureFilterHelp': '只有满足此筛选的行才计入该度量(例如 won_amount = sum(amount) where stage = won)。', + 'engine.inspector.dataset.derived': '派生 —— 由其他度量计算', + 'engine.inspector.dataset.operation': '运算', + 'engine.inspector.dataset.operands': '操作数(其他度量)', + 'engine.inspector.dataset.derivedNeedTwo': '{op} 需要恰好选择 2 个度量。', + 'engine.inspector.dataset.derivedNeedOne': '{op} 至少需要选择 1 个度量。', + 'engine.inspector.dataset.addOtherMeasuresFirst': '请先添加其他度量。', + 'engine.inspector.dataset.advanced': '高级', + 'engine.inspector.dataset.displayFormat': '显示格式', + 'engine.inspector.dataset.decimals': '小数位数', + 'engine.inspector.dataset.currency': '货币', + 'engine.inspector.dataset.sample': '示例:{sample}', + 'engine.inspector.dataset.relWarning': '关联 {rel} 不在「包含的关联」中。', + 'engine.inspector.dataset.relWarningAdd': '添加它', + 'engine.inspector.dataset.filterAdvanced': '高级筛选(嵌套 / OR)—— 请在「{source}」标签页中编辑。', + 'engine.inspector.dataset.sourceTab': '源码', + 'engine.inspector.dataset.filterConditionsOne': '{count} 个条件', + 'engine.inspector.dataset.filterConditionsOther': '{count} 个条件', + 'engine.inspector.dataset.addFilter': '+ 添加筛选…', + 'engine.inspector.dataset.filterNeedsObject': '先选择基础对象,才能添加筛选条件。', }; export default STUDIO_ZH;