From 64cf83439f64a151e2c2e00435368861a156c412 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 16:30:09 +0000 Subject: [PATCH 1/2] feat(ui): the remaining engine.inspector rows and engine.packages, engine.form, engine.edit move into the studio namespace (#25922 slice S3) 551 en and 560 zh keys leave app-shell's designer table for @object-ui/i18n/namespaces/studio, text unchanged; the nine zh-only engine.packages.form.help rows move as zh-only and stay tOptional reads. No call site changes. Claude-Session: https://claude.ai/code/session_01JQwxbX9LnJhjTQPJEmoBYi Co-authored-by: Claude --- .../src/views/metadata-admin/i18n.ts | 1295 ----------------- ui/packages/i18n/src/namespaces/studio/en.ts | 672 +++++++++ ui/packages/i18n/src/namespaces/studio/zh.ts | 629 ++++++++ 3 files changed, 1301 insertions(+), 1295 deletions(-) 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 97f0d3ad3fe..e3a19eb3467 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -331,29 +331,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.sharingRule.clone.sourceOff': 'The declared rule "{source}" is switched off.', 'engine.sharingRule.clone.openCopy': 'Open the new rule', 'engine.sharingRule.clone.done': 'Done', - 'engine.edit.capabilityReadOnly': - 'Read-only for your account: changing this item requires the `manage_metadata` capability, which the platform administrator holds.', - 'engine.edit.save': 'Save', - 'engine.edit.publish': 'Publish', - 'engine.edit.publishBlockedDirty': 'Save first, then publish.', - 'engine.edit.discardDraft': 'Discard draft', - 'engine.edit.discardDraftConfirm': 'Discard the pending draft for {item}?', - // objectui#13333 — the in-app confirm's body under the title above. - 'engine.edit.discardDraftConfirmBody': 'The published version stays as it is; only the unpublished changes are removed.', - 'engine.edit.draftDiscarded': 'Draft discarded: {item}', - 'engine.edit.draftPending': 'Pending changes — save lives in a draft. Click Publish to release.', - 'engine.edit.rollback': 'Rollback to this version', - 'engine.edit.rollbackConfirm': 'Restore version {version}? This writes the historical body back as the current published overlay.', - 'engine.edit.rollbackConfirmTitle': 'Restore {item} to version {version}?', - 'engine.edit.reset': 'Reset overlay', - // objectui#13333 — the reset / delete confirm is an in-app dialog whose title - // names the item by its type label and its own label, and whose body names - // the machine `type/name` the DELETE addresses. - 'engine.edit.resetConfirm': 'Reset the overlay on {typeLabel} "{item}"?', - 'engine.edit.resetConfirmBody': 'Your customization of {type}/{name} is removed and its code-defined version applies again.', - 'engine.edit.delete': 'Delete', - 'engine.edit.deleteConfirm': 'Delete {typeLabel} "{item}"?', - 'engine.edit.deleteConfirmBody': '{type}/{name} is deleted. This cannot be undone.', // objectui#13333 — the datasource list's delete confirm and the type-level // action confirm, both in-app dialogs naming what they act on. 'engine.datasource.deleteTitle': 'Delete datasource "{item}"?', @@ -419,39 +396,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.datasource.syncFailed': 'Failed: {failures}', 'engine.typeAction.confirmTitle': '{action} on "{item}"?', 'engine.typeAction.confirmTitleNoItem': '{action}?', - 'engine.edit.lockFull': 'This item is locked and cannot be edited or deleted.', - 'engine.edit.lockNoOverlay': 'This item is locked and cannot be edited.', - 'engine.edit.lockNoDelete': 'This item is locked and cannot be deleted.', - // objectui#5024 — the headline for a lock state this console has no - // sentence for. Reachable without any change here: `layered()` casts the - // wire value through unchecked, so a newer server can send a fifth state - // today. Names the raw token because the operator who sees it is the only - // one who can report which state their server sent. - 'engine.edit.lockUnknown': - 'This item is locked, but this console does not recognise the lock state \u2018{state}\u2019 — it may come from a newer server. Some operations will be blocked.', - 'engine.edit.history': 'History', - 'engine.edit.auditTab': 'Audit log', - 'engine.edit.auditCount': 'events', - 'engine.edit.auditEmptyTitle': 'No audit events yet', - 'engine.edit.auditEmptyDescription': 'No save, publish, rollback, delete or reset attempts have been recorded for this item. Once someone tries to change it (allowed or denied), the attempt will appear here.', - // …and the FAILED read (objectui#5169). The empty copy above is a POSITIVE - // CLAIM about the record — true for a read that completed and found nothing, - // a lie for one that never completed — and it used to render next to the - // failure banner and a measured `0 events`. This copy states only that the - // read did not complete and makes no claim in either direction about whether - // attempts were recorded. Any reword must keep that property; "no attempts - // have been recorded" is exactly the sentence this state exists to avoid. - 'engine.edit.auditErrorTitle': 'Audit trail could not be read', - 'engine.edit.auditErrorDescription': - 'The audit read did not complete, so whether anything has been recorded for this item is unknown. This is not a statement that no attempts were made — use Refresh to ask again.', - 'engine.edit.auditColTime': 'Time', - 'engine.edit.auditColActor': 'Actor', - 'engine.edit.auditColOperation': 'Operation', - 'engine.edit.auditColOutcome': 'Outcome', - 'engine.edit.auditColLock': 'Lock', - 'engine.edit.auditColNote': 'Note', - 'engine.edit.refresh': 'Refresh', - 'engine.edit.layers': 'Layers', 'engine.layers.code': 'Code', 'engine.layers.overlay': 'Overlay', 'engine.layers.effective': 'Effective', @@ -468,30 +412,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.layers.diff.noChanges': 'Overlay matches the code value — no field-level differences.', 'engine.layers.diff.noBaseline': 'No code-level artifact to compare against. All fields are runtime-authored.', 'engine.layers.diff.summary': '{modified} modified · {added} added · {removed} removed', - 'engine.edit.overlaidCount': '{count} overlaid', - 'engine.edit.noOverlay': 'no overlay', - 'engine.edit.references': 'References', - 'engine.edit.form': 'Form', - 'engine.edit.edit': 'Edit', - 'engine.edit.editOverlay': 'Edit overlay', - 'engine.edit.createNew': 'Create new', - 'engine.edit.detail': 'Detail', - 'engine.edit.preview': 'Preview', - 'engine.edit.designer': 'Designer', - 'engine.edit.related': 'Related', - 'engine.edit.hideInspector': 'Hide inspector', - 'engine.edit.showInspector': 'Show inspector', - 'engine.edit.noChanges': 'No changes to save', - 'engine.edit.inspector': 'Inspector', - 'engine.edit.inspector.properties': 'Properties', - 'engine.edit.inspector.source': 'Source', - 'engine.edit.fullscreen': 'Fullscreen', - 'engine.edit.exitFullscreen': 'Exit fullscreen', - 'engine.edit.designMode': 'Design', - 'engine.edit.previewMode': 'Preview', - // 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)', // 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 @@ -526,251 +446,9 @@ const ENGINE_STRINGS_EN: Record = { 'engine.dashboardPreview.saveTitle': 'Save title', 'engine.dashboardPreview.renameWidget': 'Rename widget', 'engine.dashboardPreview.clearSelection': 'Clear selection', - // Flow edge (connection) inspector - 'engine.inspector.flowEdge.kind': 'Connection', - 'engine.inspector.flowEdge.close': 'Close connection', - 'engine.inspector.flowEdge.missing': 'This connection no longer exists.', - 'engine.inspector.flowEdge.source': 'From', - 'engine.inspector.flowEdge.target': 'To', - 'engine.inspector.flowEdge.routing': 'Routing', - 'engine.inspector.flowEdge.branch': 'Branch', - 'engine.inspector.flowEdge.label': 'Branch label', - 'engine.inspector.flowEdge.labelHint': 'e.g. approve / reject', - 'engine.inspector.flowEdge.condition': 'Condition', - 'engine.inspector.flowEdge.conditionHint': 'CEL expression — taken when it evaluates true', - 'engine.inspector.flowEdge.isDefault': 'Default branch (else)', - 'engine.inspector.flowEdge.hint': 'The engine follows this connection when its branch label is selected or its condition is met. The default branch is taken when no other matches.', - 'engine.inspector.flowEdge.remove': 'Remove connection', - 'engine.inspector.flowEdge.approvalBranch': 'Approval branch', - 'engine.inspector.flowEdge.branchApprove': 'Approve', - 'engine.inspector.flowEdge.branchReject': 'Reject', - 'engine.inspector.flowEdge.branchRevise': 'Revise — send back', - 'engine.inspector.flowEdge.branchCustom': '— Custom —', - // objectui#10748 — the word after ` · ` on a decision branch whose condition - // is empty or `true` (the default branch), in the branch picker. - 'engine.inspector.flowEdge.branchDefault': 'default', - 'engine.inspector.flowEdge.connection': 'Connection', - 'engine.inspector.flowEdge.type': 'Type', - 'engine.inspector.flowEdge.typeDefault': 'Normal', - 'engine.inspector.flowEdge.typeConditional': 'Conditional', - 'engine.inspector.flowEdge.typeFault': 'Fault (error path)', - 'engine.inspector.flowEdge.typeBack': 'Back-edge (revise loop)', - 'engine.inspector.flowEdge.backHint': 'A back-edge re-enters an earlier node to close a loop (e.g. an approval revise loop). It is traversed normally at run time but excluded from cycle validation.', - // Workflow action inspector - 'engine.inspector.workflowAction.kind': 'Action', - 'engine.inspector.workflowAction.close': 'Close action', - 'engine.inspector.workflowAction.type': 'Action Type', - 'engine.inspector.workflowAction.name': 'Name', - 'engine.inspector.workflowAction.config': 'Config (JSON)', - 'engine.inspector.workflowAction.remove': 'Remove action', - // View column inspector - 'engine.inspector.viewColumn.kind': 'Column', - 'engine.inspector.viewColumn.close': 'Close column', - 'engine.inspector.viewColumn.header': 'Header', - 'engine.inspector.viewColumn.accessorKey': 'Field key', - 'engine.inspector.viewColumn.width': 'Width', - 'engine.inspector.viewColumn.align': 'Align', - 'engine.inspector.viewColumn.sortable': 'Sortable', - 'engine.inspector.viewColumn.filterable': 'Filterable', - 'engine.inspector.viewColumn.remove': 'Remove column', - 'engine.inspector.viewColumn.outlineLabel': 'Columns', - // View variant inspector (the View "home" panel — also hosted by the - // runtime ObjectView's right-rail view editor) - 'engine.inspector.view.kind': 'View', - 'engine.inspector.view.close': 'Close', - 'engine.inspector.view.label': 'Label', - 'engine.inspector.view.labelPlaceholder': 'e.g. All Leads', - 'engine.inspector.view.type': 'View type', - 'engine.inspector.view.object': 'Object', - 'engine.inspector.view.noSchema': 'Spec schema unavailable — basic properties only.', - // objectui#6900 — an object-required field inside a predicate-gated form - // section (ruling 5749269225): name both facts, then the three remedies. - 'engine.inspector.view.gatedRequired.title': 'Save is blocked: a required field sits in a conditional section.', - 'engine.inspector.view.gatedRequired.issue': 'Field "{fieldLabel}" ({field}) is required on object "{object}", but it sits in section "{section}", which is shown only when {predicate}. The server never evaluates that condition, so whenever the section is hidden the form cannot be completed — the record save is refused over a field the user cannot see.', - 'engine.inspector.view.gatedRequired.remedies': 'Fix it one of three ways:', - 'engine.inspector.view.gatedRequired.remedyMove': 'Move "{fieldLabel}" into a section that has no visibleWhen.', - 'engine.inspector.view.gatedRequired.remedyDrop': 'Remove visibleWhen from section "{section}".', - 'engine.inspector.view.gatedRequired.remedyUnrequire': 'Make "{fieldLabel}" not required on object "{object}".', - // Conditional formatting editor (list/grid views) - 'engine.inspector.view.cf.title': 'Conditional formatting', - 'engine.inspector.view.cf.add': 'Add rule', - 'engine.inspector.view.cf.empty': 'No rules. Add one to color rows by a CEL condition.', - 'engine.inspector.view.cf.rule': 'Rule', - 'engine.inspector.view.cf.when': 'When (CEL)', - 'engine.inspector.view.cf.background': 'Background', - 'engine.inspector.view.cf.text': 'Text', - 'engine.inspector.view.cf.border': 'Border', - 'engine.inspector.view.cf.preview': 'Preview', - 'engine.inspector.view.cf.remove': 'Remove rule', - 'engine.inspector.view.cf.moveUp': 'Move up', - 'engine.inspector.view.cf.moveDown': 'Move down', // ConditionBuilder raw-expression mode (CEL editor, #1582) 'engine.condition.celLabel': 'CEL expression', 'engine.condition.advancedHint': 'Advanced expression — Builder only supports simple AND/OR conditions.', - // ConditionBuilder row mode (objectui#10586). `≥` / `≤` stay symbols. - 'engine.inspector.condition.builder': 'Builder', - 'engine.inspector.condition.expression': 'Expression', - 'engine.inspector.condition.always': 'Always — no condition.', - 'engine.inspector.condition.and': 'AND', - 'engine.inspector.condition.or': 'OR', - 'engine.inspector.condition.subjectPlaceholder': 'field / context', - 'engine.inspector.condition.remove': 'Remove condition', - 'engine.inspector.condition.valuePlaceholder': 'value', - 'engine.inspector.condition.add': 'Add condition', - 'engine.inspector.condition.op.equals': 'equals', - 'engine.inspector.condition.op.notEquals': 'not equals', - 'engine.inspector.condition.op.greaterThan': 'greater than', - 'engine.inspector.condition.op.lessThan': 'less than', - 'engine.inspector.condition.op.truthy': 'is set / true', - 'engine.inspector.condition.op.falsy': 'is empty / false', - // objectui#11894 — the value-less pair, worded by the subject's declared - // type; the two rows above stay for a subject the catalog does not type. - 'engine.inspector.condition.op.isTrue': 'is true', - 'engine.inspector.condition.op.isFalse': 'is false', - 'engine.inspector.condition.op.isNotEmpty': 'is not empty', - 'engine.inspector.condition.op.isEmpty': 'is empty', - // objectui#10748 — the lookup filter's operator words the four above do not - // cover (`ObjectFieldInspector` `LOOKUP_OPERATORS`, which reads the four as - // well; the symbol a label leads with stays outside the word). - 'engine.inspector.condition.op.atLeast': 'at least', - 'engine.inspector.condition.op.atMost': 'at most', - 'engine.inspector.condition.op.contains': 'contains', - 'engine.inspector.condition.op.inAnyOf': 'in (any of)', - 'engine.inspector.condition.op.notIn': 'not in', - 'engine.inspector.view.type.grid': 'Table / List', - 'engine.inspector.view.type.kanban': 'Kanban', - 'engine.inspector.view.type.calendar': 'Calendar', - 'engine.inspector.view.type.gallery': 'Gallery', - 'engine.inspector.view.type.gantt': 'Gantt', - 'engine.inspector.view.type.timeline': 'Timeline', - 'engine.inspector.view.type.map': 'Map', - 'engine.inspector.view.type.chart': 'Chart', - // Report default ("home") inspector - 'engine.inspector.report.kind': 'Report', - 'engine.inspector.report.close': 'Close report', - 'engine.inspector.report.name': 'Name', - 'engine.inspector.report.nameHint': 'snake_case identifier (cannot change after create)', - 'engine.inspector.report.namePlaceholder': 'e.g. pipeline_by_stage', - 'engine.inspector.report.label': 'Label', - 'engine.inspector.report.labelPlaceholder': 'e.g. Pipeline by Stage', - 'engine.inspector.report.type': 'Report type', - 'engine.inspector.report.type.tabular': 'Tabular', - 'engine.inspector.report.type.summary': 'Summary', - 'engine.inspector.report.type.matrix': 'Matrix', - 'engine.inspector.report.type.joined': 'Joined', - 'engine.inspector.report.dataset': 'Dataset', - 'engine.inspector.report.datasetPlaceholder': 'e.g. sales_metrics', - 'engine.inspector.report.values': 'Values (measures)', - 'engine.inspector.report.valuesEmpty': 'No measures yet. Add one from the dataset below.', - 'engine.inspector.report.rows': 'Rows (dimensions)', - 'engine.inspector.report.rowsEmpty': 'No dimensions yet. Add one from the dataset below.', - 'engine.inspector.report.columnsAcross': 'Columns (across dimensions)', - 'engine.inspector.report.columnsAcrossEmpty': 'No across dimensions yet — the matrix pivots rows × columns.', - 'engine.inspector.report.chart': 'Chart', - 'engine.inspector.report.chartType': 'Chart type', - 'engine.inspector.report.chartNone': 'None (table only)', - 'engine.inspector.report.chartTitle': 'Chart title', - 'engine.inspector.report.chartX': 'X-Axis (dimension)', - 'engine.inspector.report.chartY': 'Y-Axis (measure)', - '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', - '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. - 'engine.inspector.hook.bodyLang.expression': 'Expression (L1)', - 'engine.inspector.hook.bodyLang.js': 'Sandboxed JS (L2)', - 'engine.inspector.hook.basics': 'Basics', - 'engine.inspector.hook.label': 'Label', - 'engine.inspector.hook.labelPlaceholder': 'Human-readable name', - 'engine.inspector.hook.name': 'Name', - 'engine.inspector.hook.namePlaceholder': 'snake_case identifier', - 'engine.inspector.hook.objects': 'Object(s) this hook fires on', - 'engine.inspector.hook.allObjects': 'All objects (*)', - 'engine.inspector.hook.noObjects': 'No objects found — publish an object, then pick it here.', - 'engine.inspector.hook.pickObject': 'Pick at least one object (or All objects).', - 'engine.inspector.hook.outsideReach': 'These belong to other packages or to the platform (sys_…). A hook on one runs on that object’s events wherever they happen, not only in this package.', - 'engine.inspector.hook.allObjectsReach': 'Runs on the events of every object — other packages’ objects and the platform’s own (sys_…) tables included.', - 'engine.inspector.hook.events': 'Events', - 'engine.inspector.hook.eventsHint': 'Which lifecycle events invoke this hook.', - 'engine.inspector.hook.eventsWrite': 'Write', - 'engine.inspector.hook.eventsQuery': 'Query', - 'engine.inspector.hook.pickEvent': 'Select at least one event.', - 'engine.inspector.hook.function': 'Function', - 'engine.inspector.hook.functionHint': 'The handler that runs when the hook fires.', - 'engine.inspector.hook.language': 'Language', - 'engine.inspector.hook.handlerBody': 'Handler body', - 'engine.inspector.hook.bodyHint.expression': 'A single L1 expression evaluated against the record / ctx.', - 'engine.inspector.hook.bodyHint.js': 'Runs in the sandbox as (ctx) => Promise.', - 'engine.inspector.hook.options': 'Options', - 'engine.inspector.hook.priority': 'Priority', - '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.', - // Dashboard default (home) inspector - 'engine.inspector.dashboard.kind': 'Dashboard', - 'engine.inspector.dashboard.close': 'Close dashboard', - 'engine.inspector.dashboard.label': 'Label', - 'engine.inspector.dashboard.labelPlaceholder': 'e.g. Sales Overview', - 'engine.inspector.dashboard.description': 'Description', - 'engine.inspector.dashboard.descriptionPlaceholder': 'Optional summary shown in the header', - 'engine.inspector.dashboard.widgets': 'Widgets', - '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.', - // Flow reference picker — ReferenceCombobox / FlowReferenceField (objectui#10586) - 'engine.inspector.reference.pickFromRecords': 'Pick from records', - 'engine.inspector.reference.pickFromRegistry': 'Pick from declared items', - 'engine.inspector.reference.enterManually': 'Enter value manually', - 'engine.inspector.reference.managerPlaceholder': 'Resolved automatically', - 'engine.inspector.reference.managerHint': "Resolved at runtime from the submitter's manager — no value needed.", - 'engine.inspector.reference.queueHint': "The queue's machine name. Its members when the step opens are the approvers; an empty queue adds nobody.", - 'engine.inspector.reference.fieldsOf': 'Fields of {object}.', - 'engine.inspector.reference.setTriggerObject': 'Set the flow’s trigger object (on the Start node) to list fields.', - 'engine.inspector.reference.actionsOf': 'Actions of {connector}.', - 'engine.inspector.reference.chooseConnector': 'Choose a Connector above to list its actions.', - 'engine.inspector.reference.declarative': 'declarative', - // objectui#11788 — the notify node's recipient picker (`FlowRecipientsField`). - 'engine.inspector.recipients.kindLabel': 'Recipient type', - 'engine.inspector.recipients.kind.field': 'Record field', - 'engine.inspector.recipients.kind.user': 'User', - 'engine.inspector.recipients.kind.team': 'Team', - 'engine.inspector.recipients.kind.email': 'Email address', - 'engine.inspector.recipients.kind.expression': 'Expression', - 'engine.inspector.recipients.kind.custom': 'Other', - 'engine.inspector.recipients.placeholder.field': 'Field of the trigger record', - 'engine.inspector.recipients.placeholder.user': 'Pick a user', - 'engine.inspector.recipients.placeholder.team': 'Pick a team', - 'engine.inspector.recipients.placeholder.email': 'name@example.com', - 'engine.inspector.recipients.placeholder.expression': 'CEL, e.g. ownerId', - 'engine.inspector.recipients.placeholder.custom': 'Saved as typed, e.g. role:admin', - 'engine.inspector.recipients.add': 'Add recipient', - 'engine.inspector.recipients.remove': 'Remove recipient', - 'engine.inspector.recipients.empty': 'No recipients yet.', - 'engine.inspector.recipients.notEmail': 'Not an email address — the messaging service would read it as a user id.', - 'engine.inspector.recipients.kind.emailField': 'Email field', - 'engine.inspector.recipients.placeholder.emailField': 'Email field of the trigger record', - 'engine.inspector.recipients.emailFieldHint': 'Mails the address in this field of the trigger record, by email only — for a person who is not a user. It is the node\u2019s only recipient.', - 'engine.inspector.recipients.emailFieldNotEmail': 'Not an email field — this recipient must name an email-typed field of the trigger record.', - 'engine.inspector.recipients.emailFieldChannels': 'This recipient is sent by email only: the node\u2019s channels must be email alone, or the node is refused.', - 'engine.inspector.recipients.emailFieldChannelsFix': 'Send by email only', - 'engine.inspector.recipients.fieldOnEmailNode': 'This node sends email: the address in this field must belong to a user, or the run fails. To mail the address itself, use the Email field type.', - // objectui#11788 — the create/update record nodes' field-value keys, picked from the target object. - 'engine.inspector.fieldMap.chooseObject': 'Choose the Object above to list its fields.', - // objectui#11783 — the shared object picker (inspectors/ObjectPicker.tsx). - 'engine.inspector.objectPicker.groupPackage': 'This package', - 'engine.inspector.objectPicker.groupOther': 'Other objects', - 'engine.inspector.objectPicker.groupSystem': 'System objects', - 'engine.inspector.objectPicker.showSystem': 'Show system objects ({count})', - 'engine.inspector.objectPicker.hideSystem': 'Hide system objects', - 'engine.inspector.objectPicker.noMatch': 'No object matches. Press Enter to keep what you typed.', - // Add affordances (used by OutlineStrip + custom previews) - 'engine.inspector.add.widget': 'Add widget', - 'engine.inspector.add.block': 'Add block', - 'engine.inspector.add.node': 'Add node', - 'engine.inspector.add.step': 'Add step', - 'engine.inspector.add.action': 'Add action', - 'engine.inspector.add.nav': 'Add nav item', // Flow designer add-node palette (search box + recents, #1943) 'engine.flowPalette.search': 'Search nodes…', 'engine.flowPalette.empty': 'No matching nodes.', @@ -1593,220 +1271,12 @@ const ENGINE_STRINGS_EN: Record = { 'engine.flowProblems.connectRefused.missingTarget': 'Cannot connect: this flow has no node "{target}".', 'engine.flowProblems.connectRefused.self': 'Cannot connect "{source}" to itself.', 'engine.flowProblems.connectRefused.repeat': '"{source}" already connects to "{target}". Select that connection to change it.', - // References side panel (ResourceEditPage) empty state. - 'engine.edit.refsScanning': 'Scanning references…', - 'engine.edit.refsEmptyTitle': 'No references found', - 'engine.edit.refsEmptyDesc': 'Nothing in the metadata graph points at this item. Safe to delete.', - // #25916 — the references table's column headers. - 'engine.edit.refsColFromType': 'From type', - 'engine.edit.refsColFromName': 'From name', - 'engine.edit.refsColPath': 'Path', - // …and its FAILED state (objectui#5110), which must never be confusable with - // the empty one above: the copy states that the check did not complete and - // deliberately makes no claim — in either direction — about whether deleting - // is safe, because the question was not answered. Any wording change here - // must keep that property; "no references were found" is exactly the - // sentence this state exists to avoid. - 'engine.edit.refsErrorTitle': 'Reference check failed', - 'engine.edit.refsErrorDesc': - 'The reference scan did not complete, so what depends on this item is unknown. This is not a statement that deleting it is safe — run the check again before you decide.', - 'engine.edit.refsRetry': 'Retry check', - // Destructive-change (force save) dialog (ResourceEditPage). - 'engine.edit.destructiveTitle': 'Destructive change detected', - 'engine.edit.destructiveDesc': - 'The framework refused this save because it would drop or narrow data already in use. Review the issues and confirm to override.', - 'engine.edit.forcing': 'Forcing…', - // History page / panel (ResourceHistoryPage). - 'engine.edit.historySubtitle': 'Version history', - 'engine.edit.historyEvents': 'Events', - 'engine.edit.historyEmptyTitle': 'No history yet', - 'engine.edit.historyEmptyDesc': - 'This item has never been edited via an overlay. The first save will create the initial history record.', - // Reorder buttons (used in InspectorShell header) - 'engine.inspector.reorder.up': 'Move up', - 'engine.inspector.reorder.down': 'Move down', - // InspectorComboField defaults (objectui#10586). `{value}` is the typed - // text, drawn in a code span by the combo. - 'engine.inspector.combo.search': 'Search or type…', - 'engine.inspector.combo.noMatch': 'No match — keep typing to use a custom value.', - 'engine.inspector.combo.loading': 'Loading…', - 'engine.inspector.combo.useCustom': 'Use {value}', - 'engine.edit.overlay': 'overlay', - 'engine.edit.readOnlyBanner': 'Viewing in read-only mode. Click {edit} to make changes.', - // objectui#12141 — `{flag}` renders `OS_METADATA_WRITABLE`, the one spelling - // the server reads. The sentence used to offer a second remedy, a registry - // flip of the type's overlay flag, and nobody can perform it: the registry is - // a TypeScript literal in `@objectstack/spec` that no deployment edits. The - // hatch is named with its reach (ADR-0131 D6): it opens the type's items that - // no package ships, and never an item a package ships. - 'engine.edit.readOnlyTypeBanner': - 'This metadata type is read-only for safety: it ships executable code or sensitive configuration and cannot be overlaid at runtime. Edit the source in your package and redeploy. An operator can set {flag} to include {type} (use with caution); that opens only the items of this type that no package ships, never an item a package ships.', - 'engine.edit.artifactLockedBanner': - 'This {type} is provided by an installed package, so it is read-only at runtime. To change it, edit it in its source package and republish — or create a new {type} from scratch.', 'engine.badge.createOnly': 'create-only', 'engine.repeater.empty': 'No items. Click + to add.', 'engine.badge.writable': 'writable', 'engine.badge.readOnly': 'read-only', - 'engine.edit.readOnly': 'Read-only (runtime overrides disabled).', - 'engine.edit.loading': 'Loading', - 'engine.edit.bespokeDesigner': 'Designer', - 'engine.edit.readOnlyHint': - 'Read-only — this metadata type does not allow runtime overrides. Edit the source in the package and redeploy.', - 'engine.edit.unsaved': 'Unsaved', - 'engine.edit.unsavedHint': 'You have unsaved changes.', - 'engine.edit.diagnostics.title': - 'This metadata does not match the spec — {count} validation error(s).', - 'engine.edit.diagnostics.warnTitle': '{count} warning(s) — review recommended.', - 'engine.edit.diagnostics.more': '+{count} more…', - 'engine.edit.loadFailed': 'Failed to load {type}/{name}: {message}', - 'engine.edit.unsavedLeaveConfirm': - 'You have unsaved changes. Leave this page anyway?', - 'engine.edit.saving': 'Saving…', - 'engine.edit.savedAt': 'Saved {time}', - 'engine.edit.autoSaveOn': 'Auto-save is on — click to disable', - 'engine.edit.autoSaveOff': 'Auto-save is off — click to enable', - 'engine.edit.autoSavingShortly': 'Auto-saving…', - 'engine.edit.destructive': 'Destructive change', - 'engine.edit.destructiveHint': - 'The change would break existing references. Review the issues and confirm to force-save.', - 'engine.edit.forceSave': 'Force save', 'engine.cancel': 'Cancel', 'engine.close': 'Close', - 'engine.form.selectEllipsis': 'Select…', - 'engine.form.add': 'Add', - 'engine.form.addItem': 'Add item', - 'engine.form.addRow': 'Add row', - 'engine.form.remove': 'Remove', - 'engine.form.removeRow': 'Remove row', - 'engine.form.dragToReorder': 'Drag to reorder', - 'engine.form.arrayPlaceholder': 'comma, separated, values', - // Accessible name for the hex mirror beside the native colour picker. The - // picker itself is named by the field's visible label (objectui#4871); this - // second input edits the same value and needs a name of its own. - 'engine.form.colorHex': 'Hex value', - 'engine.form.loadingObjects': 'Loading objects…', - 'engine.form.noObjects': 'object_name (no objects detected)', - 'engine.form.selectObject': 'Select object…', - 'engine.form.selectComponent': 'Select component…', - 'engine.form.noComponents': 'component_id (no components on this page yet)', - 'engine.form.addObjects': 'Add objects…', - 'engine.form.loadingFields': 'Loading fields…', - 'engine.form.selectObjectFirst': '(Select an object first)', - 'engine.form.selectField': 'Select field…', - 'engine.form.addFields': 'Add fields…', - 'engine.form.noObjectBound': 'No object bound', - // The FAILURE state shared by every option picker (objectui#5170). It must - // never be confusable with the empty-state copy around it — `noObjects` - // ("no objects detected") is a MEASUREMENT and `noObjectBound` names a CAUSE, - // and on a failed load both are false. This copy states only that the list - // could not be loaded and makes no claim in either direction about whether - // options exist, because the question was not answered. Any reword must keep - // that property: "no options were found" is exactly the sentence this state - // exists to avoid. - 'engine.form.loadingOptions': 'Loading options…', - 'engine.form.optionsLoadFailedTitle': 'Options could not be loaded', - 'engine.form.optionsLoadFailedDesc': - 'This list did not load, so it is not a statement that no options exist. Reload the editor to ask again.', - 'engine.form.none': '— None —', - 'engine.form.notInObject': '(not in object)', - // The flags a designer picker puts on a STORED value its roster does not - // offer (objectui#9652). Each is a bare suffix, like `notInObject` above, - // which `widgets.tsx` draws in a muted span of its own. A picker that shows - // the value and its flag as ONE option label joins them through - // `flaggedValue`, so the order of the two and the gap between them belong to - // the locale rather than to the call site: zh sets no space before the - // full-width bracket its flags open with. - 'engine.form.notFound': '(not found)', - 'engine.form.deprecated': '(deprecated)', - // objectui#10448: a stored tier outside the org-membership-level enum, and a - // hook's selected object that the live catalog does not list. - 'engine.form.invalid': '(invalid)', - 'engine.form.notPublished': '(not published)', - 'engine.form.flaggedValue': '{value} {flag}', - 'engine.form.searchIcons': 'Search icons…', - 'engine.form.chooseIcon': 'Choose an icon', - 'engine.form.iconsTruncated': 'Showing the first {shown} of {total} — type to narrow.', - 'engine.form.noMatchingIcons': 'No matching icons.', - // objectui#12531 — the icon picker's inline warning on a stored name that - // matches no icon in the catalog it offers. `{name}` is the stored value. - 'engine.form.unknownIcon': 'No icon is named “{name}”. Choose one from the list.', - 'engine.form.keep': 'Keep', - 'engine.form.addField': 'Add field…', - 'engine.form.addFieldPlain': 'Add field', - 'engine.form.searchFields': 'Search fields…', - 'engine.form.allFieldsAdded': 'All fields added', - // objectui#12126 — the `ref-multi:permission` picker's add control: its - // accessible name, its placeholder, and the placeholder once every declared - // permission set is picked. - 'engine.form.addPermissionSetPlain': 'Add permission set', - 'engine.form.addPermissionSet': 'Add permission set…', - 'engine.form.allPermissionSetsAdded': 'All permission sets added', - 'engine.form.noObjectFields': 'No object fields ({error}).', - // objectui#10862, slice 4 — the not-found `error` the field and dataset - // catalogue hooks (`useObjectFields`, `useDatasetSemantics`) publish, read - // in the designer locale where the hook returns it. Every other `error` - // they carry is the transport's own message and passes through. - 'engine.form.objectNotFound': 'Object not found', - 'engine.form.datasetNotFound': 'Dataset not found', - 'engine.form.noMatchingFields': 'No matching fields.', - 'engine.form.noFieldsOnObject': 'No fields on this object.', - 'engine.form.added': 'Added', - 'engine.form.tagsPlaceholder': 'Type and press Enter…', - 'engine.form.noRows': 'No rows. Click + to add.', - 'engine.form.masterDetailSchemaError': 'master-detail widget requires items.properties on the JSON schema (or an anyOf branch that has them).', - 'engine.form.missingField': 'Field {field} declared in form layout but missing from schema. Skipping.', - 'engine.form.fallbackJson': 'widget {widget} — falling back to JSON until a custom renderer is registered.', - 'engine.form.keyRequired': 'Key is required', - 'engine.form.keyExists': 'Key "{key}" already exists', - 'engine.form.keyPattern': 'Key must match {pattern}', - 'engine.form.invalidJson': 'Invalid JSON', - 'engine.form.rootJsonObject': 'Root must be a JSON object', - // objectui#8218 — the metadata-admin form is now the Studio 「界面」 property - // panel an AI-build maker edits, so every literal it still printed in English - // landed inside an otherwise-Chinese surface. Swept from `widgets.tsx` + - // `SchemaForm.tsx`; keys below are the ones that sweep introduced. - 'engine.form.machineName': 'Machine name', - 'engine.form.sectionN': 'Section {n}', - 'engine.form.notConfigured': 'Not configured', - 'engine.form.configure': 'Configure', - 'engine.form.removeNamed': 'Remove {name}', - 'engine.form.moveUp': 'Move up', - 'engine.form.moveDown': 'Move down', - 'engine.form.noneShort': 'none', - 'engine.form.filterModeNone': 'None', - 'engine.form.filterModeTabs': 'Tabs', - 'engine.form.filterModeDropdown': 'Dropdown', - 'engine.form.filterElement': 'Filter element', - 'engine.form.removeField': 'Remove field', - 'engine.form.addFilterField': '+ Add filter field…', - 'engine.form.bindObjectForFilterFields': 'Bind a source object to pick filter fields.', - 'engine.form.tabLabel': 'Tab label', - 'engine.form.moveTabUp': 'Move tab up', - 'engine.form.moveTabDown': 'Move tab down', - 'engine.form.removeTab': 'Remove tab', - 'engine.form.bindObjectForTabRules': 'Bind a source object to build tab filter rules.', - 'engine.form.bindObjectForConditions': 'Bind a source object to add filter conditions.', - 'engine.form.addActionButtonPlain': 'Add action button', - 'engine.form.addActionButton': '+ Add action button…', - 'engine.form.allActionsAdded': 'All actions added', - 'engine.form.bindObjectForActions': 'Bind a source object to pick actions', - 'engine.form.color': 'Color', - // A notation, not prose — but it still reaches the reader, so it goes - // through the table like everything else rather than being hard-coded. A - // locale that writes hex differently now has somewhere to say so. - 'engine.form.colorHexPlaceholder': '#RRGGBB', - 'engine.form.secretSetTypeToReplace': '•••••••• set — type to replace', - 'engine.form.secretSetLeaveBlank': '•••••••• set — leave blank to keep', - 'engine.form.enterAValue': 'Enter a value', - 'engine.form.secretValue': 'Secret value', - 'engine.form.hideValue': 'Hide value', - 'engine.form.revealValue': 'Reveal value', - 'engine.form.clear': 'Clear', - 'engine.form.anOption': 'an option', - 'engine.form.selectDepToConfigure': 'Select {dep} to configure.', - 'engine.form.noConfigNeeded': 'No configuration needed for "{value}".', - 'engine.form.readOnly': 'read-only', - 'engine.form.loadingEditor': 'Loading editor…', 'engine.validation.invalid': 'Invalid input', 'engine.validation.pattern': 'Invalid string: must match pattern {pattern}', 'engine.validation.expectedStringUndefined': 'Required text value', @@ -1826,146 +1296,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.embedded.noSchema': 'No form schema is registered for {type}. Edit the raw JSON below; saving will splice it back into {target}.', 'engine.embedded.readOnly': 'Read-only', 'engine.embedded.noPathHint': 'No embedded path registered — cannot determine where to write this item back.', - 'engine.packages.title': 'Packages', - 'engine.packages.description': 'Author, publish, and manage the packages installed in this system.', - 'engine.packages.refresh': 'Refresh', - 'engine.packages.import': 'Import', - 'engine.packages.importing': 'Importing…', - 'engine.packages.new': 'New Package', - 'engine.packages.moreActions': 'More actions', - 'engine.packages.search': 'Search packages…', - 'engine.packages.showPlatform': 'Show platform packages ({count})', - 'engine.packages.empty': 'No packages', - 'engine.packages.emptyCreate': 'Create your first package to start authoring metadata.', - 'engine.packages.emptyFiltered': 'No packages match your filters.', - 'engine.packages.installedApps.title': 'Installed Apps', - 'engine.packages.installedApps.empty': 'No apps installed from the marketplace yet', - 'engine.packages.installedApps.emptyHint': 'Apps you install from the marketplace are listed here.', - 'engine.packages.loadFailed': 'Failed to load packages', - 'engine.packages.col.name': 'Name', - 'engine.packages.col.version': 'Version', - 'engine.packages.col.scope': 'Scope', - 'engine.packages.col.status': 'Status', - 'engine.packages.scope.project': 'Read-only · code', - 'engine.packages.scope.system': 'System', - 'engine.packages.scope.cloud': 'Cloud', - 'engine.packages.scope.writable': 'Writable', - 'engine.packages.scope.readOnly': 'Read-only', - 'engine.packages.scope.unknown': 'Package "{id}" was refused: the server did not say whether it is writable.', - 'engine.packages.col.source': 'Source', - 'engine.packages.col.actions': 'Actions', - 'engine.packages.source.code': 'Code', - 'engine.packages.source.marketplace': 'Marketplace', - 'engine.packages.source.authored': 'Authored', - 'engine.packages.source.showAll': 'Show all', - 'engine.packages.source.unknown': 'Package "{id}" was refused: the server sent no source for it, or one that is not code, marketplace or authored.', - 'engine.packages.source.filterUnknown': 'Unknown source filter "{value}". Use code, marketplace or authored.', - 'engine.packages.source.filterRepeated': 'The source filter is given more than once ({value}). Give one of code, marketplace or authored.', - 'engine.packages.status.enabled': 'Enabled', - 'engine.packages.status.disabled': 'Disabled', - 'engine.packages.import.invalidJson': 'Selected file is not valid JSON.', - 'engine.packages.import.invalidPackage': 'Invalid package file: missing "id" or "name".', - 'engine.packages.import.success': 'Imported "{id}". Refresh the app switcher to use it.', - 'engine.packages.import.failed': 'Import failed', - 'engine.packages.create.title': 'New Package', - 'engine.packages.create.description': 'Create a writable package to author and publish your own metadata. The id should be reverse-domain (e.g. {example}).', - 'engine.packages.create.id': 'Package ID', - 'engine.packages.create.namespace': 'Object namespace', - 'engine.packages.create.name': 'Display name', - // objectui#11792 — the create dialog's Package ID field (PackageIdInput), - // which judges by the spec's id rule rather than the landing form's own. - 'engine.packages.idRule.formatHint': 'Use a reverse-domain id like {example}: two or more parts separated by dots, each made of lowercase letters, digits and inner hyphens. Underscores are not allowed.', - 'engine.packages.idRule.strippedNotice': 'Unsupported characters were removed — allowed: lowercase letters, digits, dots and hyphens', - 'engine.packages.idRule.derived': 'Filled in from the display name until you edit it.', - 'engine.packages.idRule.notDerived': 'The display name has no Latin letters to make an ID from. Type one, like {example}.', - 'engine.packages.nsRule.derived': 'Filled in from the package ID until you edit it.', - 'engine.packages.nsRule.required': 'Required: every object name in this package starts with it.', - 'engine.packages.versionRule.default': 'A starting version, kept until you edit it.', - 'engine.packages.create.version': 'Version', - 'engine.packages.create.versionInvalid': 'Use semantic version, e.g. 0.1.0', - 'engine.packages.create.creating': 'Creating…', - 'engine.packages.create.submit': 'Create package', - 'engine.packages.create.failed': 'Failed to create package', - 'engine.packages.create.exists': 'A package with this id already exists.', - // objectstack#8270 — the server refuses package writes without the ADR-0066 - // `manage_metadata` capability, and its refusal is an English sentence that - // reached a zh console verbatim. Deployments that withhold the capability do - // so deliberately, so this states the posture rather than inviting a retry. - 'engine.packages.noCapability': 'You do not have permission to manage packages in this system — it requires the “Manage Metadata” permission.', - 'engine.packages.form.basics': 'Basics', - 'engine.packages.form.advanced': 'Advanced', - 'engine.packages.form.namespace': 'Namespace', - 'engine.packages.form.defaultDatasource': 'Default datasource', - 'engine.packages.form.scope': 'Scope', - 'engine.packages.form.dependencies': 'Dependencies', - // objectui#13278 — the Dependencies row's key/value editor. - 'engine.packages.dependencies.package': 'Package', - 'engine.packages.dependencies.range': 'Version range', - 'engine.packages.dependencies.add': 'Add dependency', - 'engine.packages.dependencies.remove': 'Remove dependency', - 'engine.packages.dependencies.empty': 'No dependencies.', - 'engine.packages.dependencies.nameRequired': 'Enter a package name.', - 'engine.packages.dependencies.duplicate': 'This package is already listed.', - 'engine.packages.view.title': 'Package info', - 'engine.packages.detail.viewInfo': 'View info', - 'engine.packages.detail.type': 'Type', - 'engine.packages.detail.description': 'Description', - 'engine.packages.detail.browseMetadata': "Browse this package's metadata", - 'engine.packages.detail.pendingChanges': 'Pending changes', - 'engine.packages.detail.pendingHint': 'Drafted, not yet published. Publish the whole app, or review each below.', - 'engine.packages.detail.publishApp': 'Publish app ({count})', - 'engine.packages.detail.discardChanges': 'Discard changes ({count})', - 'engine.packages.detail.kernelReadOnly': 'This is a platform kernel package. Authoring actions are disabled.', - 'engine.packages.detail.actions': 'Actions', - 'engine.packages.detail.edit': 'Edit', - 'engine.packages.edit.title': 'Edit package', - 'engine.packages.edit.save': 'Save changes', - 'engine.packages.edit.saving': 'Saving…', - 'engine.packages.edit.saved': 'Package updated', - 'engine.packages.edit.failed': 'Failed to update package', - 'engine.packages.detail.publish': 'Publish', - 'engine.packages.detail.publishing': 'Publishing…', - 'engine.packages.detail.revert': 'Revert', - 'engine.packages.detail.enable': 'Enable', - 'engine.packages.detail.disable': 'Disable', - 'engine.packages.detail.export': 'Export', - 'engine.packages.detail.exporting': 'Exporting…', - 'engine.packages.detail.deleteApp': 'Delete app', - 'engine.packages.detail.deleting': 'Deleting…', - 'engine.packages.detail.discarding': 'Discarding…', - 'engine.packages.detail.actionFailed': 'Action failed', - 'engine.packages.detail.publishBlocked': 'Publish blocked by {count} validation error(s).', - 'engine.packages.detail.nothingToPublish': 'Nothing to publish.', - 'engine.packages.detail.published': 'Package published.', - 'engine.packages.detail.publishDraftsPartial': 'Published {published}; {failed} failed.', - 'engine.packages.detail.publishDraftsRolledBack': 'Nothing was published — the batch rolled back (all-or-nothing): {cause}', - 'engine.packages.detail.publishDraftsOk': 'App published — all drafts are now live.', - 'engine.packages.detail.reverted': 'Reverted to last published state.', - 'engine.packages.detail.discardDraftsPartial': 'Discarded {discarded}; {failed} failed.', - 'engine.packages.detail.discardDraftsOk': 'All pending changes discarded — back to the published version.', - 'engine.packages.detail.discardTitle': 'Discard all pending changes ({count})?', - 'engine.packages.detail.discardDescription': 'Every draft in this app is discarded and the app goes back to its last published version. Published metadata and data are not touched. Discarded drafts cannot be recovered.', - 'engine.packages.detail.deleteTitle': 'Delete "{name}"?', - 'engine.packages.detail.deleteDescription': 'This removes every object, view, dashboard and app in the package. Choose what happens to the records those objects hold. This cannot be undone.', - 'engine.packages.detail.deleteModeStructure': 'Delete the structure, keep the records', - 'engine.packages.detail.deleteModeStructureHint': "The package's metadata is removed. The database tables its objects created, and every record in them, are kept.", - 'engine.packages.detail.deleteModeAll': 'Delete the structure and the data', - 'engine.packages.detail.deleteModeAllHint': 'Also drops the database tables its objects created, with every record in them.', - 'engine.packages.detail.deleteTypeName': 'Type "{name}" to confirm', - 'engine.packages.detail.duplicateGo': 'Create copy', - 'engine.packages.detail.deleteFailed': 'Delete failed', - 'engine.packages.detail.disabled': 'Package disabled.', - 'engine.packages.detail.enabled': 'Package enabled.', - 'engine.packages.detail.exported': 'Package exported.', - 'engine.packages.detail.duplicate': 'Duplicate', - 'engine.packages.detail.duplicating': 'Duplicating…', - 'engine.packages.detail.duplicated': 'Package duplicated into a new base.', - 'engine.packages.detail.adoptOrphans': 'Adopt loose items', - 'engine.packages.detail.adopting': 'Adopting…', - 'engine.packages.detail.adoptTitle': 'Adopt loose items into "{name}"?', - 'engine.packages.detail.adoptDescription': 'Every package-less (loose) metadata item in this system moves into this base. Items that already belong to a package stay where they are.', - 'engine.packages.detail.adoptedOne': '{count} item adopted into this base.', - 'engine.packages.detail.adoptedOther': '{count} items adopted into this base.', 'engine.quickfind.placeholder': "Find metadata types or items… (try 'view', 'account')", 'engine.quickfind.empty': 'Type to search across all metadata types.', 'engine.quickfind.title': 'Quick Find', @@ -3097,25 +2427,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.sharingRule.clone.sourceOff': '已声明的规则「{source}」已停用。', 'engine.sharingRule.clone.openCopy': '打开新规则', 'engine.sharingRule.clone.done': '完成', - 'engine.edit.capabilityReadOnly': - '您的账号只能查看:修改此条目需要 `manage_metadata` 能力,该能力由平台管理员持有。', - 'engine.edit.save': '保存', - 'engine.edit.publish': '发布', - 'engine.edit.publishBlockedDirty': '请先保存再发布。', - 'engine.edit.discardDraft': '丢弃草稿', - 'engine.edit.discardDraftConfirm': '丢弃 {item} 的待发布草稿?', - 'engine.edit.discardDraftConfirmBody': '已发布的版本保持不变,仅移除未发布的更改。', - 'engine.edit.draftDiscarded': '已丢弃 {item} 的待发布草稿', - 'engine.edit.draftPending': '存在未发布的草稿 — 保存仅写入草稿,点击"发布"使其生效。', - 'engine.edit.rollback': '回滚到此版本', - 'engine.edit.rollbackConfirm': '恢复版本 {version}?该历史内容将被写回作为当前已发布的覆盖层。', - 'engine.edit.rollbackConfirmTitle': '将 {item} 恢复到版本 {version}?', - 'engine.edit.reset': '重置覆盖', - 'engine.edit.resetConfirm': '重置{typeLabel}“{item}”的覆盖层?', - 'engine.edit.resetConfirmBody': '将移除您对 {type}/{name} 的自定义,恢复使用代码中定义的版本。', - 'engine.edit.delete': '删除', - 'engine.edit.deleteConfirm': '删除{typeLabel}“{item}”?', - 'engine.edit.deleteConfirmBody': '{type}/{name} 将被删除。此操作不可撤销。', 'engine.datasource.deleteTitle': '删除数据源“{item}”?', 'engine.datasource.deleteBody': '连接 {name} 将被移除。此操作不可撤销。', 'engine.datasource.delete': '删除', @@ -3176,30 +2487,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.datasource.syncFailed': '失败:{failures}', 'engine.typeAction.confirmTitle': '对“{item}”执行{action}?', 'engine.typeAction.confirmTitleNoItem': '执行{action}?', - 'engine.edit.lockFull': '该元数据已锁定,不可编辑或删除。', - 'engine.edit.lockNoOverlay': '该元数据已锁定,不可编辑。', - 'engine.edit.lockNoDelete': '该元数据已锁定,不可删除。', - 'engine.edit.lockUnknown': - '该元数据已锁定,但当前控制台无法识别锁状态“{state}”——它可能来自更新版本的服务端。部分操作将被阻止。', - 'engine.edit.history': '历史', - 'engine.edit.auditTab': '审计日志', - 'engine.edit.auditCount': '条记录', - 'engine.edit.auditEmptyTitle': '暂无审计记录', - 'engine.edit.auditEmptyDescription': '该元数据尚未发生任何保存、发布、回滚、删除或重置操作。一旦有人尝试修改(无论允许或拒绝),都会在此显示。', - // The zh half of the audit failure state (objectui#5169). Same rule as the en - // table above: the empty copy is a positive claim about the record, so this - // copy must state only that the read did not complete. - 'engine.edit.auditErrorTitle': '审计记录读取失败', - 'engine.edit.auditErrorDescription': - '审计读取没有完成,因此无法得知这一项是否有过记录。这并不表示没有发生过任何尝试 —— 请使用「刷新」重新读取。', - 'engine.edit.auditColTime': '时间', - 'engine.edit.auditColActor': '操作人', - 'engine.edit.auditColOperation': '操作', - 'engine.edit.auditColOutcome': '结果', - 'engine.edit.auditColLock': '锁状态', - 'engine.edit.auditColNote': '备注', - 'engine.edit.refresh': '刷新', - 'engine.edit.layers': '层次', 'engine.layers.code': '代码', 'engine.layers.overlay': '覆盖', 'engine.layers.effective': '生效', @@ -3216,28 +2503,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.layers.diff.noChanges': '覆盖层与代码值一致——没有字段级差异。', 'engine.layers.diff.noBaseline': '没有代码级工件可比对——所有字段均为运行时创建。', 'engine.layers.diff.summary': '{modified} 修改 · {added} 新增 · {removed} 删除', - 'engine.edit.overlaidCount': '{count} 处覆盖', - 'engine.edit.noOverlay': '无覆盖', - 'engine.edit.references': '引用关系', - 'engine.edit.form': '表单', - 'engine.edit.edit': '编辑', - 'engine.edit.editOverlay': '编辑覆盖层', - 'engine.edit.createNew': '新建', - 'engine.edit.detail': '详情', - 'engine.edit.preview': '预览', - 'engine.edit.designer': '可视化编辑器', - 'engine.edit.related': '关联', - 'engine.edit.hideInspector': '隐藏属性面板', - 'engine.edit.showInspector': '显示属性面板', - 'engine.edit.noChanges': '没有需要保存的更改', - 'engine.edit.inspector': '属性', - 'engine.edit.inspector.properties': '属性', - 'engine.edit.inspector.source': '源码', - 'engine.edit.fullscreen': '全屏', - 'engine.edit.exitFullscreen': '退出全屏', - 'engine.edit.designMode': '设计', - 'engine.edit.previewMode': '预览', - 'engine.inspector.draftSuffix': '(草稿)', 'engine.widgetPicker.search': '搜索组件…', 'engine.widgetPicker.noMatches': '没有匹配项。', 'engine.widgetPicker.category.kpi': '单值', @@ -3264,242 +2529,9 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.dashboardPreview.saveTitle': '保存标题', 'engine.dashboardPreview.renameWidget': '重命名组件', 'engine.dashboardPreview.clearSelection': '取消选择', - // Flow edge (connection) inspector - 'engine.inspector.flowEdge.kind': '连线', - 'engine.inspector.flowEdge.close': '关闭连线', - 'engine.inspector.flowEdge.missing': '该连线已不存在。', - 'engine.inspector.flowEdge.source': '起点', - 'engine.inspector.flowEdge.target': '终点', - 'engine.inspector.flowEdge.routing': '路由', - 'engine.inspector.flowEdge.branch': '分支', - 'engine.inspector.flowEdge.label': '分支标签', - 'engine.inspector.flowEdge.labelHint': '例如 approve / reject', - 'engine.inspector.flowEdge.condition': '条件', - 'engine.inspector.flowEdge.conditionHint': 'CEL 表达式 —— 为真时走此连线', - 'engine.inspector.flowEdge.isDefault': '默认分支(else)', - 'engine.inspector.flowEdge.hint': '当此连线的分支标签被选中、或其条件成立时,引擎会走这条连线。其余都不匹配时走默认分支。', - 'engine.inspector.flowEdge.remove': '删除连线', - 'engine.inspector.flowEdge.approvalBranch': '审批分支', - 'engine.inspector.flowEdge.branchApprove': '通过', - 'engine.inspector.flowEdge.branchReject': '驳回', - 'engine.inspector.flowEdge.branchRevise': '退回修改', - 'engine.inspector.flowEdge.branchCustom': '—— 自定义 ——', - 'engine.inspector.flowEdge.branchDefault': '默认', - 'engine.inspector.flowEdge.connection': '连线', - 'engine.inspector.flowEdge.type': '类型', - 'engine.inspector.flowEdge.typeDefault': '普通', - 'engine.inspector.flowEdge.typeConditional': '条件', - 'engine.inspector.flowEdge.typeFault': '故障(错误路径)', - 'engine.inspector.flowEdge.typeBack': '回边(退回修改环)', - 'engine.inspector.flowEdge.backHint': '回边会重新进入更早的节点以闭合一个环(例如审批的退回修改环)。它在运行时正常通行,但不参与环路校验。', - // Workflow action inspector - 'engine.inspector.workflowAction.kind': '动作', - 'engine.inspector.workflowAction.close': '关闭动作', - 'engine.inspector.workflowAction.type': '动作类型', - 'engine.inspector.workflowAction.name': '名称', - 'engine.inspector.workflowAction.config': '配置(JSON)', - 'engine.inspector.workflowAction.remove': '删除动作', - // View column inspector - 'engine.inspector.viewColumn.kind': '列', - 'engine.inspector.viewColumn.close': '关闭列', - 'engine.inspector.viewColumn.header': '表头', - 'engine.inspector.viewColumn.accessorKey': '字段键', - 'engine.inspector.viewColumn.width': '宽度', - 'engine.inspector.viewColumn.align': '对齐', - 'engine.inspector.viewColumn.sortable': '可排序', - 'engine.inspector.viewColumn.filterable': '可过滤', - 'engine.inspector.viewColumn.remove': '删除列', - 'engine.inspector.viewColumn.outlineLabel': '列', - // View variant inspector - 'engine.inspector.view.kind': '视图', - 'engine.inspector.view.close': '关闭', - 'engine.inspector.view.label': '名称', - 'engine.inspector.view.labelPlaceholder': '例如:全部线索', - 'engine.inspector.view.type': '视图类型', - 'engine.inspector.view.object': '对象', - 'engine.inspector.view.noSchema': 'spec 模式不可用 —— 仅显示基础属性。', - // objectui#6900 — 见英文表同名条目。 - 'engine.inspector.view.gatedRequired.title': '保存已被拦截:必填字段位于条件区块中。', - 'engine.inspector.view.gatedRequired.issue': '字段“{fieldLabel}”({field})在对象“{object}”上是必填的,但它位于区块“{section}”中,而该区块仅在 {predicate} 时显示。服务端从不计算这个条件,因此区块被隐藏时表单无法完成——保存记录会被拒绝,报错指向一个用户看不到的字段。', - 'engine.inspector.view.gatedRequired.remedies': '可用以下三种方式之一修复:', - 'engine.inspector.view.gatedRequired.remedyMove': '把“{fieldLabel}”移到没有 visibleWhen 的区块中。', - 'engine.inspector.view.gatedRequired.remedyDrop': '去掉区块“{section}”上的 visibleWhen。', - 'engine.inspector.view.gatedRequired.remedyUnrequire': '在对象“{object}”上把“{fieldLabel}”改为非必填。', - // 条件格式化编辑器(列表/表格视图) - 'engine.inspector.view.cf.title': '条件格式化', - 'engine.inspector.view.cf.add': '添加规则', - 'engine.inspector.view.cf.empty': '暂无规则。添加一条以按 CEL 条件为行着色。', - 'engine.inspector.view.cf.rule': '规则', - 'engine.inspector.view.cf.when': '条件(CEL)', - 'engine.inspector.view.cf.background': '背景色', - 'engine.inspector.view.cf.text': '文字色', - 'engine.inspector.view.cf.border': '边框色', - 'engine.inspector.view.cf.preview': '预览', - 'engine.inspector.view.cf.remove': '删除规则', - 'engine.inspector.view.cf.moveUp': '上移', - 'engine.inspector.view.cf.moveDown': '下移', // ConditionBuilder 裸表达式模式(CEL 编辑器,#1582) 'engine.condition.celLabel': 'CEL 表达式', 'engine.condition.advancedHint': '高级表达式 —— 搭建器仅支持简单的 AND/OR 条件。', - // ConditionBuilder row mode (objectui#10586). The join words follow the - // packs' own `filterBuilder.and` / `filterBuilder.or`. - 'engine.inspector.condition.builder': '搭建器', - 'engine.inspector.condition.expression': '表达式', - 'engine.inspector.condition.always': '始终生效 —— 无条件。', - 'engine.inspector.condition.and': '且', - 'engine.inspector.condition.or': '或', - 'engine.inspector.condition.subjectPlaceholder': '字段 / 上下文', - 'engine.inspector.condition.remove': '移除条件', - 'engine.inspector.condition.valuePlaceholder': '值', - 'engine.inspector.condition.add': '添加条件', - 'engine.inspector.condition.op.equals': '等于', - 'engine.inspector.condition.op.notEquals': '不等于', - 'engine.inspector.condition.op.greaterThan': '大于', - 'engine.inspector.condition.op.lessThan': '小于', - 'engine.inspector.condition.op.truthy': '已设置 / 为真', - 'engine.inspector.condition.op.falsy': '为空 / 为假', - 'engine.inspector.condition.op.isTrue': '为真', - 'engine.inspector.condition.op.isFalse': '为假', - 'engine.inspector.condition.op.isNotEmpty': '不为空', - 'engine.inspector.condition.op.isEmpty': '为空', - 'engine.inspector.condition.op.atLeast': '至少', - 'engine.inspector.condition.op.atMost': '至多', - 'engine.inspector.condition.op.contains': '包含', - 'engine.inspector.condition.op.inAnyOf': '属于(任一)', - 'engine.inspector.condition.op.notIn': '不属于', - 'engine.inspector.view.type.grid': '表格 / 列表', - 'engine.inspector.view.type.kanban': '看板', - 'engine.inspector.view.type.calendar': '日历', - 'engine.inspector.view.type.gallery': '画廊', - 'engine.inspector.view.type.gantt': '甘特图', - 'engine.inspector.view.type.timeline': '时间线', - 'engine.inspector.view.type.map': '地图', - 'engine.inspector.view.type.chart': '图表', - // Report default ("home") inspector - 'engine.inspector.report.kind': '报表', - 'engine.inspector.report.close': '关闭报表', - 'engine.inspector.report.name': '名称', - 'engine.inspector.report.nameHint': 'snake_case 标识符(创建后不可修改)', - 'engine.inspector.report.namePlaceholder': '例如:pipeline_by_stage', - 'engine.inspector.report.label': '显示名', - 'engine.inspector.report.labelPlaceholder': '例如:按阶段统计的销售管道', - 'engine.inspector.report.type': '报表类型', - 'engine.inspector.report.type.tabular': '表格', - 'engine.inspector.report.type.summary': '汇总', - 'engine.inspector.report.type.matrix': '矩阵', - 'engine.inspector.report.type.joined': '联合', - 'engine.inspector.report.dataset': '数据集', - 'engine.inspector.report.datasetPlaceholder': '例如:sales_metrics', - 'engine.inspector.report.values': '值(度量)', - 'engine.inspector.report.valuesEmpty': '还没有度量。从下方数据集中添加。', - 'engine.inspector.report.rows': '行(维度)', - 'engine.inspector.report.rowsEmpty': '还没有维度。从下方数据集中添加。', - 'engine.inspector.report.columnsAcross': '列(横向维度)', - 'engine.inspector.report.columnsAcrossEmpty': '还没有横向维度——矩阵按 行 × 列 透视。', - 'engine.inspector.report.chart': '图表', - 'engine.inspector.report.chartType': '图表类型', - 'engine.inspector.report.chartNone': '无(仅表格)', - 'engine.inspector.report.chartTitle': '图表标题', - 'engine.inspector.report.chartX': 'X 轴(维度)', - 'engine.inspector.report.chartY': 'Y 轴(度量)', - 'engine.inspector.report.noSchema': '规格 schema 不可用 —— 仅显示基础属性。', - // Trailing section for fields the live server has but the bundled spec lacks. - 'engine.inspector.moreFields': '更多字段', - 'engine.inspector.hook.kind': '钩子', - // Hook default (home) inspector (objectui#10586). - 'engine.inspector.hook.bodyLang.expression': '表达式(L1)', - 'engine.inspector.hook.bodyLang.js': '沙箱 JS(L2)', - 'engine.inspector.hook.basics': '基本信息', - 'engine.inspector.hook.label': '显示名', - 'engine.inspector.hook.labelPlaceholder': '便于阅读的名称', - 'engine.inspector.hook.name': '名称', - 'engine.inspector.hook.namePlaceholder': 'snake_case 标识符', - 'engine.inspector.hook.objects': '此钩子触发的对象', - 'engine.inspector.hook.allObjects': '所有对象(*)', - 'engine.inspector.hook.noObjects': '未找到对象 —— 请先发布一个对象,再在此处选择。', - 'engine.inspector.hook.pickObject': '请至少选择一个对象(或「所有对象」)。', - 'engine.inspector.hook.outsideReach': '这些对象属于其他软件包或平台(sys_…)。钩子挂在其中某个对象上时,该对象的事件无论在哪里发生都会触发它,而不只是在本软件包内。', - 'engine.inspector.hook.allObjectsReach': '会在每个对象的事件上运行 —— 包括其他软件包的对象和平台自身的(sys_…)表。', - 'engine.inspector.hook.events': '事件', - 'engine.inspector.hook.eventsHint': '哪些生命周期事件会调用此钩子。', - 'engine.inspector.hook.eventsWrite': '写入', - 'engine.inspector.hook.eventsQuery': '查询', - 'engine.inspector.hook.pickEvent': '请至少选择一个事件。', - 'engine.inspector.hook.function': '函数', - 'engine.inspector.hook.functionHint': '钩子触发时运行的处理函数。', - 'engine.inspector.hook.language': '语言', - 'engine.inspector.hook.handlerBody': '处理函数主体', - 'engine.inspector.hook.bodyHint.expression': '针对 record / ctx 求值的单个 L1 表达式。', - 'engine.inspector.hook.bodyHint.js': '在沙箱中以 (ctx) => Promise 的形式运行。', - 'engine.inspector.hook.options': '选项', - 'engine.inspector.hook.priority': '优先级', - 'engine.inspector.hook.async': '异步运行(提交之后)', - 'engine.inspector.hook.condition': '仅在以下条件成立时运行(可选 CEL)', - 'engine.inspector.hook.moreFieldsHint': '高级 / 不常用的属性。', - // Dashboard default (home) inspector - 'engine.inspector.dashboard.kind': '仪表盘', - 'engine.inspector.dashboard.close': '关闭仪表盘', - 'engine.inspector.dashboard.label': '显示名', - 'engine.inspector.dashboard.labelPlaceholder': '例如:销售总览', - 'engine.inspector.dashboard.description': '描述', - 'engine.inspector.dashboard.descriptionPlaceholder': '可选,显示在头部的摘要', - 'engine.inspector.dashboard.widgets': '组件', - 'engine.inspector.dashboard.widgetsEmpty': '还没有组件。在下方添加。', - 'engine.inspector.dashboard.removeWidget': '删除组件', - 'engine.inspector.dashboard.noSchema': '规格 schema 不可用 —— 仅显示基础属性。', - // Flow reference picker — ReferenceCombobox / FlowReferenceField (objectui#10586) - 'engine.inspector.reference.pickFromRecords': '从记录中选择', - 'engine.inspector.reference.pickFromRegistry': '从已声明的项中选择', - 'engine.inspector.reference.enterManually': '手动输入值', - 'engine.inspector.reference.managerPlaceholder': '自动解析', - 'engine.inspector.reference.managerHint': '运行时根据提交人的上级自动解析 —— 无需填写。', - 'engine.inspector.reference.queueHint': '队列的机器名。步骤开启时队列的成员即为审批人;空队列不会添加任何人。', - 'engine.inspector.reference.fieldsOf': '{object} 的字段。', - 'engine.inspector.reference.setTriggerObject': '请先在开始节点上设置流程的触发对象,才能列出字段。', - 'engine.inspector.reference.actionsOf': '{connector} 的动作。', - 'engine.inspector.reference.chooseConnector': '请先在上方选择连接器,才能列出其动作。', - 'engine.inspector.reference.declarative': '声明式', - // objectui#11788 — 通知节点的收件人选择器(`FlowRecipientsField`)。 - 'engine.inspector.recipients.kindLabel': '收件人类型', - 'engine.inspector.recipients.kind.field': '记录字段', - 'engine.inspector.recipients.kind.user': '用户', - 'engine.inspector.recipients.kind.team': '团队', - 'engine.inspector.recipients.kind.email': '邮箱地址', - 'engine.inspector.recipients.kind.expression': '表达式', - 'engine.inspector.recipients.kind.custom': '其他', - 'engine.inspector.recipients.placeholder.field': '触发记录的字段', - 'engine.inspector.recipients.placeholder.user': '选择用户', - 'engine.inspector.recipients.placeholder.team': '选择团队', - 'engine.inspector.recipients.placeholder.email': 'name@example.com', - 'engine.inspector.recipients.placeholder.expression': 'CEL 表达式,例如 ownerId', - 'engine.inspector.recipients.placeholder.custom': '按原样保存,例如 role:admin', - 'engine.inspector.recipients.add': '添加收件人', - 'engine.inspector.recipients.remove': '移除收件人', - 'engine.inspector.recipients.empty': '还没有收件人。', - 'engine.inspector.recipients.notEmail': '这不是邮箱地址 —— 消息服务会把它当作用户 id。', - 'engine.inspector.recipients.kind.emailField': '邮箱字段', - 'engine.inspector.recipients.placeholder.emailField': '触发记录的邮箱字段', - 'engine.inspector.recipients.emailFieldHint': '向触发记录该字段中的地址发送邮件,仅走邮件渠道 —— 用于不是用户的人。它是本节点唯一的收件人。', - 'engine.inspector.recipients.emailFieldNotEmail': '这不是邮箱类型的字段 —— 这种收件人必须指向触发记录的邮箱类型字段。', - 'engine.inspector.recipients.emailFieldChannels': '这种收件人只走邮件渠道:本节点的渠道必须只有 email,否则节点会被拒收。', - 'engine.inspector.recipients.emailFieldChannelsFix': '只走邮件', - 'engine.inspector.recipients.fieldOnEmailNode': '本节点会发邮件:该字段中的地址必须属于某个用户,否则运行失败。要直接给该地址发邮件,请改用「邮箱字段」类型。', - // objectui#11788 — 新建/更新记录节点的字段值键,从目标对象中选取。 - 'engine.inspector.fieldMap.chooseObject': '请先在上方选择对象,才能列出它的字段。', - // objectui#11783 — the shared object picker (inspectors/ObjectPicker.tsx). - 'engine.inspector.objectPicker.groupPackage': '本软件包', - 'engine.inspector.objectPicker.groupOther': '其他对象', - 'engine.inspector.objectPicker.groupSystem': '系统对象', - 'engine.inspector.objectPicker.showSystem': '显示系统对象({count})', - 'engine.inspector.objectPicker.hideSystem': '隐藏系统对象', - 'engine.inspector.objectPicker.noMatch': '没有匹配的对象。按 Enter 保留输入的内容。', - // Add affordances - 'engine.inspector.add.widget': '添加组件', - 'engine.inspector.add.block': '添加区块', - 'engine.inspector.add.node': '添加节点', - 'engine.inspector.add.step': '添加步骤', - 'engine.inspector.add.action': '添加动作', - 'engine.inspector.add.nav': '添加导航项', // Flow designer add-node palette (search box + recents, #1943) 'engine.flowPalette.search': '搜索节点…', 'engine.flowPalette.empty': '没有匹配的节点。', @@ -4265,178 +3297,12 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.flowProblems.connectRefused.missingTarget': '无法连线:此流程中没有节点 “{target}”。', 'engine.flowProblems.connectRefused.self': '无法将 “{source}” 连到它自身。', 'engine.flowProblems.connectRefused.repeat': '“{source}” 已经连到 “{target}”。请选中那条连线再修改。', - // 引用关系侧栏(ResourceEditPage)空态。 - 'engine.edit.refsScanning': '正在扫描引用…', - 'engine.edit.refsEmptyTitle': '未找到引用', - 'engine.edit.refsEmptyDesc': '元数据图中没有任何项指向它,可以安全删除。', - 'engine.edit.refsColFromType': '来源类型', - 'engine.edit.refsColFromName': '来源名称', - 'engine.edit.refsColPath': '路径', - // …以及它的失败态(objectui#5110):必须与上面的空态不可混淆 —— 文案只说明检查 - // 没有完成,对「删除是否安全」不做任何方向的断言,因为这个问题根本没有被回答。 - // 后续改写务必保留这一性质;「未找到引用」正是此状态要避免的那句话。 - 'engine.edit.refsErrorTitle': '引用检查失败', - 'engine.edit.refsErrorDesc': - '引用扫描没有完成,因此无法得知哪些内容依赖此项。这并不表示删除它是安全的 —— 请重新执行检查后再做决定。', - 'engine.edit.refsRetry': '重新检查', - // 破坏性变更(强制保存)对话框(ResourceEditPage)。 - 'engine.edit.destructiveTitle': '检测到破坏性变更', - 'engine.edit.destructiveDesc': '框架拒绝了本次保存,因为它会丢弃或收窄正在使用的数据。请检查这些问题并确认以强制覆盖。', - 'engine.edit.forcing': '强制保存中…', - // 历史页 / 面板(ResourceHistoryPage)。 - 'engine.edit.historySubtitle': '版本历史', - 'engine.edit.historyEvents': '事件', - 'engine.edit.historyEmptyTitle': '暂无历史', - 'engine.edit.historyEmptyDesc': '此项从未通过覆盖层编辑过。首次保存将创建初始历史记录。', - // Reorder buttons - 'engine.inspector.reorder.up': '上移', - 'engine.inspector.reorder.down': '下移', - // InspectorComboField defaults (objectui#10586). - 'engine.inspector.combo.search': '搜索或输入…', - 'engine.inspector.combo.noMatch': '无匹配项 —— 继续输入即可使用自定义值。', - 'engine.inspector.combo.loading': '加载中…', - 'engine.inspector.combo.useCustom': '使用 {value}', - 'engine.edit.overlay': '覆盖', - 'engine.edit.readOnlyBanner': '当前为只读模式。点击 {edit} 进行修改。', - 'engine.edit.readOnlyTypeBanner': - '出于安全考虑,此元数据类型为只读:它包含可执行代码或敏感配置,不允许在运行时通过覆盖层修改。请编辑包内源代码后重新部署。运维可将 {flag} 设置为包含 {type}(请谨慎使用);这只会开放该类型中不由任何包提供的条目,永远不会开放由包提供的条目。', - 'engine.edit.artifactLockedBanner': - '此 {type} 来自已安装的包,因此在运行时为只读。如需修改,请在其所属包的源中编辑并重新发布——或从零新建一个 {type}。', 'engine.badge.createOnly': '仅可新建', 'engine.repeater.empty': '暂无条目。点击 + 添加。', 'engine.badge.writable': '可写', 'engine.badge.readOnly': '只读', - 'engine.edit.readOnly': '只读(运行时覆盖未启用)。', - 'engine.edit.loading': '加载中', - 'engine.edit.bespokeDesigner': '可视化编辑器', - 'engine.edit.readOnlyHint': - '只读 — 该元数据类型不支持运行时覆盖。请修改包内源代码并重新部署。', - 'engine.edit.unsaved': '未保存', - 'engine.edit.diagnostics.title': '元数据不符合规范 — 共 {count} 个校验错误。', - 'engine.edit.diagnostics.warnTitle': '{count} 个警告 — 建议复查。', - 'engine.edit.diagnostics.more': '还有 {count} 个…', - 'engine.edit.loadFailed': '加载 {type}/{name} 失败:{message}', - 'engine.edit.unsavedHint': '当前存在未保存的修改。', - 'engine.edit.unsavedLeaveConfirm': '当前存在未保存的修改,确定离开此页面吗?', - 'engine.edit.saving': '保存中…', - 'engine.edit.savedAt': '已保存 {time}', - 'engine.edit.autoSaveOn': '自动保存已开启,点击关闭', - 'engine.edit.autoSaveOff': '自动保存已关闭,点击开启', - 'engine.edit.autoSavingShortly': '即将自动保存…', - 'engine.edit.destructive': '破坏性变更', - 'engine.edit.destructiveHint': '该修改会破坏现有引用关系。请检查问题清单后确认强制保存。', - 'engine.edit.forceSave': '强制保存', 'engine.cancel': '取消', 'engine.close': '关闭', - 'engine.form.selectEllipsis': '请选择…', - 'engine.form.add': '添加', - 'engine.form.addItem': '添加项', - 'engine.form.addRow': '添加行', - 'engine.form.remove': '删除', - 'engine.form.removeRow': '删除行', - 'engine.form.dragToReorder': '拖动排序', - 'engine.form.arrayPlaceholder': '用逗号分隔多个值', - 'engine.form.colorHex': '十六进制值', - 'engine.form.loadingObjects': '正在加载对象…', - 'engine.form.noObjects': 'object_name(未检测到对象)', - 'engine.form.selectObject': '选择对象…', - 'engine.form.selectComponent': '选择组件…', - 'engine.form.noComponents': 'component_id(此页面暂无组件)', - 'engine.form.addObjects': '添加对象…', - 'engine.form.loadingFields': '正在加载字段…', - 'engine.form.selectObjectFirst': '(请先选择对象)', - 'engine.form.selectField': '选择字段…', - 'engine.form.addFields': '添加字段…', - 'engine.form.noObjectBound': '未绑定对象', - // The zh half of the shared picker failure state (objectui#5170). Same rule - // as the en table above: this copy must never be confusable with the empty - // copy around it, and must make no claim in either direction about whether - // options exist. - 'engine.form.loadingOptions': '正在加载选项…', - 'engine.form.optionsLoadFailedTitle': '选项加载失败', - 'engine.form.optionsLoadFailedDesc': - '这个列表没有加载成功,因此它并不表示不存在可选项。请重新加载编辑器后再试。', - 'engine.form.none': '— 无 —', - 'engine.form.notInObject': '(不在对象中)', - // The zh half of the unknown-value flags (objectui#9652). No space in - // `flaggedValue`: every flag opens with a full-width bracket. - 'engine.form.notFound': '(未找到)', - 'engine.form.deprecated': '(已弃用)', - 'engine.form.invalid': '(无效)', - 'engine.form.notPublished': '(未发布)', - 'engine.form.flaggedValue': '{value}{flag}', - 'engine.form.searchIcons': '搜索图标…', - 'engine.form.chooseIcon': '选择图标', - 'engine.form.iconsTruncated': '显示前 {shown} 个,共 {total} 个 —— 继续输入以缩小范围。', - 'engine.form.noMatchingIcons': '没有匹配的图标。', - 'engine.form.unknownIcon': '没有名为“{name}”的图标。请从列表中选择一个。', - 'engine.form.keep': '保留', - 'engine.form.addField': '添加字段…', - 'engine.form.addFieldPlain': '添加字段', - 'engine.form.searchFields': '搜索字段…', - 'engine.form.allFieldsAdded': '已添加全部字段', - // objectui#12126 — zh half of the `ref-multi:permission` add control. - 'engine.form.addPermissionSetPlain': '添加权限集', - 'engine.form.addPermissionSet': '添加权限集…', - 'engine.form.allPermissionSetsAdded': '已添加全部权限集', - 'engine.form.noObjectFields': '没有对象字段({error})。', - // objectui#10862, slice 4. - 'engine.form.objectNotFound': '未找到对象', - 'engine.form.datasetNotFound': '未找到数据集', - 'engine.form.noMatchingFields': '没有匹配字段。', - 'engine.form.noFieldsOnObject': '此对象暂无字段。', - 'engine.form.added': '已添加', - 'engine.form.tagsPlaceholder': '输入后按 Enter…', - 'engine.form.noRows': '暂无行。点击 + 添加。', - 'engine.form.masterDetailSchemaError': 'master-detail 组件要求 JSON Schema 上存在 items.properties(或 anyOf 分支包含该结构)。', - 'engine.form.missingField': '表单布局中声明了字段 {field},但 schema 中不存在,已跳过。', - 'engine.form.fallbackJson': '组件 {widget} 尚未注册自定义渲染器,暂时使用 JSON 编辑。', - 'engine.form.keyRequired': '键名必填', - 'engine.form.keyExists': '键名 "{key}" 已存在', - 'engine.form.keyPattern': '键名必须匹配 {pattern}', - 'engine.form.invalidJson': 'JSON 无效', - 'engine.form.rootJsonObject': '根节点必须是 JSON 对象', - // objectui#8218 — zh half of the same sweep. Wording targets the AI-build - // maker who now reads this panel, not a metadata administrator. - 'engine.form.machineName': '机器名', - 'engine.form.sectionN': '第 {n} 节', - 'engine.form.notConfigured': '尚未配置', - 'engine.form.configure': '配置', - 'engine.form.removeNamed': '删除 {name}', - 'engine.form.moveUp': '上移', - 'engine.form.moveDown': '下移', - 'engine.form.noneShort': '无', - 'engine.form.filterModeNone': '无', - 'engine.form.filterModeTabs': '标签页', - 'engine.form.filterModeDropdown': '下拉框', - 'engine.form.filterElement': '筛选控件', - 'engine.form.removeField': '删除字段', - 'engine.form.addFilterField': '+ 添加筛选字段…', - 'engine.form.bindObjectForFilterFields': '先绑定来源对象,才能挑选筛选字段。', - 'engine.form.tabLabel': '标签名称', - 'engine.form.moveTabUp': '标签上移', - 'engine.form.moveTabDown': '标签下移', - 'engine.form.removeTab': '删除标签', - 'engine.form.bindObjectForTabRules': '先绑定来源对象,才能配置标签筛选规则。', - 'engine.form.bindObjectForConditions': '先绑定来源对象,才能添加筛选条件。', - 'engine.form.addActionButtonPlain': '添加操作按钮', - 'engine.form.addActionButton': '+ 添加操作按钮…', - 'engine.form.allActionsAdded': '操作已全部添加', - 'engine.form.bindObjectForActions': '先绑定来源对象,才能挑选操作', - 'engine.form.color': '颜色', - 'engine.form.colorHexPlaceholder': '#RRGGBB', - 'engine.form.secretSetTypeToReplace': '•••••••• 已设置 —— 输入新值可替换', - 'engine.form.secretSetLeaveBlank': '•••••••• 已设置 —— 留空则保持不变', - 'engine.form.enterAValue': '请输入值', - 'engine.form.secretValue': '密钥值', - 'engine.form.hideValue': '隐藏值', - 'engine.form.revealValue': '显示值', - 'engine.form.clear': '清除', - 'engine.form.anOption': '一个选项', - 'engine.form.selectDepToConfigure': '请先选择{dep},然后再配置。', - 'engine.form.noConfigNeeded': '“{value}”无需额外配置。', - 'engine.form.readOnly': '只读', - 'engine.form.loadingEditor': '正在加载编辑器…', 'engine.validation.invalid': '输入无效', 'engine.validation.pattern': '格式无效:必须匹配 {pattern}', 'engine.validation.expectedStringUndefined': '必填,需输入文本', @@ -4455,168 +3321,7 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.embedded.noSchema': '{type} 未注册表单 schema。请在下方编辑原始 JSON;保存时会写回 {target}。', 'engine.embedded.readOnly': '只读', 'engine.embedded.noPathHint': '未注册 embeddedPath,无法判断该条目应写回哪里。', - 'engine.packages.title': '软件包', - 'engine.packages.description': '创建、发布和管理本系统中安装的软件包。', - 'engine.packages.refresh': '刷新', - 'engine.packages.import': '导入', - 'engine.packages.importing': '导入中…', - 'engine.packages.new': '新建软件包', - 'engine.packages.moreActions': '更多操作', - 'engine.packages.search': '搜索软件包…', - 'engine.packages.showPlatform': '显示平台软件包({count})', - 'engine.packages.empty': '暂无软件包', - 'engine.packages.emptyCreate': '创建第一个软件包以开始编写元数据。', - 'engine.packages.emptyFiltered': '没有匹配筛选条件的软件包。', - 'engine.packages.installedApps.title': '已安装的应用', - 'engine.packages.installedApps.empty': '尚未从应用市场安装任何应用', - 'engine.packages.installedApps.emptyHint': '从应用市场安装的应用会列在这里。', - 'engine.packages.loadFailed': '加载软件包失败', - 'engine.packages.col.name': '名称', - 'engine.packages.col.version': '版本', - 'engine.packages.col.scope': '范围', - 'engine.packages.col.status': '状态', - 'engine.packages.scope.project': '只读 · 代码包', - 'engine.packages.scope.system': '系统', - 'engine.packages.scope.cloud': '云端', - 'engine.packages.scope.writable': '可写', - 'engine.packages.scope.readOnly': '只读', - 'engine.packages.scope.unknown': '软件包「{id}」已被拒绝:服务器未说明它是否可写。', - 'engine.packages.col.source': '来源', - 'engine.packages.col.actions': '操作', - 'engine.packages.source.code': '代码', - 'engine.packages.source.marketplace': '应用市场', - 'engine.packages.source.authored': '自建', - 'engine.packages.source.showAll': '显示全部', - 'engine.packages.source.unknown': '软件包「{id}」已被拒绝:服务器未给出它的来源,或给出的来源不是 code、marketplace 或 authored 之一。', - 'engine.packages.source.filterUnknown': '未知的来源筛选值「{value}」。请使用 code、marketplace 或 authored。', - 'engine.packages.source.filterRepeated': '来源筛选给出了不止一次({value})。请只给出 code、marketplace 或 authored 之一。', - 'engine.packages.status.enabled': '已启用', - 'engine.packages.status.disabled': '已禁用', - 'engine.packages.import.invalidJson': '所选文件不是有效 JSON。', - 'engine.packages.import.invalidPackage': '无效的软件包文件:缺少 "id" 或 "name"。', - 'engine.packages.import.success': '已导入 "{id}"。刷新应用切换器后即可使用。', - 'engine.packages.import.failed': '导入失败', - 'engine.packages.create.title': '新建软件包', - 'engine.packages.create.description': '创建可写的软件包,用来编写和发布你自己的元数据。ID 建议使用反向域名格式(例如 {example})。', - 'engine.packages.create.id': '软件包 ID', - 'engine.packages.create.namespace': '对象命名空间', - 'engine.packages.create.name': '显示名称', - 'engine.packages.idRule.formatHint': '请使用反向域名格式的 ID,例如 {example}:由点分隔的两段或更多段,每段只含小写字母、数字和段内连字符,不能使用下划线。', - 'engine.packages.idRule.strippedNotice': '已移除不支持的字符——仅允许小写字母、数字、点和连字符', - 'engine.packages.idRule.derived': '编辑前会根据显示名称自动填写。', - 'engine.packages.idRule.notDerived': '显示名称里没有可用来生成 ID 的英文字母,请手动填写,例如 {example}。', - 'engine.packages.nsRule.derived': '编辑前会根据软件包 ID 自动填写。', - 'engine.packages.nsRule.required': '必填:本软件包内每个对象名称都以它开头。', - 'engine.packages.versionRule.default': '初始版本号,编辑前保持不变。', - 'engine.packages.create.version': '版本', - 'engine.packages.create.versionInvalid': '请使用语义化版本号,例如 0.1.0', - 'engine.packages.create.creating': '创建中…', - 'engine.packages.create.submit': '创建软件包', - 'engine.packages.create.failed': '创建软件包失败', - 'engine.packages.create.exists': '已存在相同 ID 的软件包。', - // objectstack#8270 — 这条正是在 zh 控制台里显示为英文原文的服务端拒绝信息。 - 'engine.packages.noCapability': '你没有在本系统管理软件包的权限 — 该操作需要「管理元数据」权限。', - 'engine.packages.form.basics': '基本信息', - 'engine.packages.form.advanced': '高级', - 'engine.packages.form.namespace': '命名空间', - 'engine.packages.form.defaultDatasource': '默认数据源', - 'engine.packages.form.scope': '范围', - 'engine.packages.form.dependencies': '依赖', - // objectui#13278 — the Dependencies row's key/value editor. - 'engine.packages.dependencies.package': '软件包', - 'engine.packages.dependencies.range': '版本范围', - 'engine.packages.dependencies.add': '添加依赖', - 'engine.packages.dependencies.remove': '移除依赖', - 'engine.packages.dependencies.empty': '暂无依赖。', - 'engine.packages.dependencies.nameRequired': '请输入软件包名称。', - 'engine.packages.dependencies.duplicate': '该软件包已在列表中。', - // `add10d8f9` — help text for the package manifest form. - // - // The LABELS above have always been translated while the help line under - // each one stayed English, so `新建软件包` rendered 显示名称 over - // "Human-readable package name" — a translated label and an untranslated - // sentence on consecutive lines of the same field. - // - // These are zh-ONLY on purpose, and the EN table deliberately has no - // counterpart. The English help text is `@objectstack/spec`'s own - // `ManifestSchema` `.describe()` (framework repo), which the form already - // reads through the spec-derived JSONSchema; `getPackageForm` looks these up - // with `tOptional`, so en-US finds nothing here and keeps falling through to - // the spec's sentence. Copying the English into this table instead would - // create a SECOND English source of truth that silently diverges the next - // time the spec is edited — the exact producer-side hazard the ObjectUI - // contract-first rule exists to stop. Only the zh rendering belongs here. - 'engine.packages.form.help.name': '软件包的可读名称。', - 'engine.packages.form.help.id': '软件包的唯一标识,采用反向域名格式。', - 'engine.packages.form.help.namespace': '简短的命名空间标识;同时是本软件包内每个对象名称的强制前缀(例如 “todo” → 对象名 “todo_task”、“todo_project”)。', - 'engine.packages.form.help.version': '软件包版本(语义化版本号)。', - 'engine.packages.form.help.type': '软件包类型。', - 'engine.packages.form.help.description': '软件包描述。', - 'engine.packages.form.help.defaultDatasource': '本软件包内所有对象的默认数据源。', - 'engine.packages.form.help.scope': '部署范围:cloud(云端)、system(系统)、project(项目)。', - 'engine.packages.form.help.dependencies': '软件包依赖。', - 'engine.packages.view.title': '软件包信息', - 'engine.packages.detail.viewInfo': '查看信息', - 'engine.packages.detail.type': '类型', - 'engine.packages.detail.description': '描述', - 'engine.packages.detail.browseMetadata': '浏览此软件包的元数据', - 'engine.packages.detail.pendingChanges': '待发布更改', - 'engine.packages.detail.pendingHint': '草稿尚未发布。可以发布整个应用,也可以逐项查看。', - 'engine.packages.detail.publishApp': '发布应用({count})', - 'engine.packages.detail.discardChanges': '丢弃更改({count})', - 'engine.packages.detail.kernelReadOnly': '这是平台内核软件包,已禁用编写操作。', - 'engine.packages.detail.actions': '操作', - 'engine.packages.detail.edit': '编辑', - 'engine.packages.edit.title': '编辑软件包', - 'engine.packages.edit.save': '保存更改', - 'engine.packages.edit.saving': '保存中…', - 'engine.packages.edit.saved': '软件包已更新', - 'engine.packages.edit.failed': '更新软件包失败', - 'engine.packages.detail.publish': '发布', - 'engine.packages.detail.publishing': '发布中…', - 'engine.packages.detail.revert': '还原', - 'engine.packages.detail.enable': '启用', - 'engine.packages.detail.disable': '禁用', - 'engine.packages.detail.export': '导出', - 'engine.packages.detail.exporting': '导出中…', - 'engine.packages.detail.deleteApp': '删除应用', - 'engine.packages.detail.deleting': '删除中…', - 'engine.packages.detail.discarding': '丢弃中…', - 'engine.packages.detail.actionFailed': '操作失败', - 'engine.packages.detail.publishBlocked': '发布被 {count} 个校验错误阻止。', - 'engine.packages.detail.nothingToPublish': '没有可发布的内容。', - 'engine.packages.detail.published': '软件包已发布。', - 'engine.packages.detail.publishDraftsPartial': '已发布 {published} 项;{failed} 项失败。', - 'engine.packages.detail.publishDraftsRolledBack': '发布未生效 — 批次已整体回滚(全有或全无):{cause}', - 'engine.packages.detail.publishDraftsOk': '应用已发布,所有草稿均已生效。', - 'engine.packages.detail.reverted': '已还原到上次发布状态。', - 'engine.packages.detail.discardDraftsPartial': '已丢弃 {discarded} 项;{failed} 项失败。', - 'engine.packages.detail.discardDraftsOk': '所有待发布更改已丢弃,已回到发布版本。', - 'engine.packages.detail.discardTitle': '丢弃全部待发布更改({count})?', - 'engine.packages.detail.discardDescription': '此应用的所有草稿都将被丢弃,应用回到上次发布的版本。已发布的元数据和数据不受影响。被丢弃的草稿无法恢复。', - 'engine.packages.detail.deleteTitle': '删除 "{name}"?', - 'engine.packages.detail.deleteDescription': '这会移除此软件包中的所有对象、视图、仪表板和应用。请选择如何处理这些对象中的记录。此操作不可撤销。', - 'engine.packages.detail.deleteModeStructure': '仅删除结构,保留记录', - 'engine.packages.detail.deleteModeStructureHint': '移除软件包的元数据。其对象创建的数据库表及其中的所有记录均保留。', - 'engine.packages.detail.deleteModeAll': '删除结构和数据', - 'engine.packages.detail.deleteModeAllHint': '同时删除其对象创建的数据库表及其中的所有记录。', - 'engine.packages.detail.deleteTypeName': '输入 "{name}" 以确认', - 'engine.packages.detail.duplicateGo': '创建副本', - 'engine.packages.detail.deleteFailed': '删除失败', - 'engine.packages.detail.disabled': '软件包已禁用。', - 'engine.packages.detail.enabled': '软件包已启用。', - 'engine.packages.detail.exported': '软件包已导出。', - 'engine.packages.detail.duplicate': '复制', - 'engine.packages.detail.duplicating': '复制中…', - 'engine.packages.detail.duplicated': '软件包已复制为新基座。', - 'engine.packages.detail.adoptOrphans': '收编散落项', - 'engine.packages.detail.adopting': '收编中…', - 'engine.packages.detail.adoptTitle': '把散落项收编进 "{name}" 吗?', - 'engine.packages.detail.adoptDescription': '本系统中所有无软件包(散落)的元数据项都会移入此基座。已属于某个软件包的项保持不动。', - // zh has one plural form, so the `…One` / `…Other` pair carries the same row. - 'engine.packages.detail.adoptedOne': '已收编 {count} 个散落项进此基座。', - 'engine.packages.detail.adoptedOther': '已收编 {count} 个散落项进此基座。', 'engine.quickfind.placeholder': '搜索元数据类型或条目…(如:view、account)', 'engine.quickfind.empty': '输入关键字以搜索所有元数据类型。', 'engine.quickfind.title': '快速查找', diff --git a/ui/packages/i18n/src/namespaces/studio/en.ts b/ui/packages/i18n/src/namespaces/studio/en.ts index 39bc00d0abc..7b740477903 100644 --- a/ui/packages/i18n/src/namespaces/studio/en.ts +++ b/ui/packages/i18n/src/namespaces/studio/en.ts @@ -1074,6 +1074,678 @@ const STUDIO_EN: Record = { 'engine.inspector.dataset.filterConditionsOther': '{count} conditions', 'engine.inspector.dataset.addFilter': '+ Add filter…', 'engine.inspector.dataset.filterNeedsObject': 'Pick a base object to add filter conditions.', + // engine.inspector (the remaining inspectors), engine.packages, engine.form + // and engine.edit (#25922 slice S3). + 'engine.edit.capabilityReadOnly': + 'Read-only for your account: changing this item requires the `manage_metadata` capability, which the platform administrator holds.', + 'engine.edit.save': 'Save', + 'engine.edit.publish': 'Publish', + 'engine.edit.publishBlockedDirty': 'Save first, then publish.', + 'engine.edit.discardDraft': 'Discard draft', + 'engine.edit.discardDraftConfirm': 'Discard the pending draft for {item}?', + // objectui#13333 — the in-app confirm's body under the title above. + 'engine.edit.discardDraftConfirmBody': 'The published version stays as it is; only the unpublished changes are removed.', + 'engine.edit.draftDiscarded': 'Draft discarded: {item}', + 'engine.edit.draftPending': 'Pending changes — save lives in a draft. Click Publish to release.', + 'engine.edit.rollback': 'Rollback to this version', + 'engine.edit.rollbackConfirm': 'Restore version {version}? This writes the historical body back as the current published overlay.', + 'engine.edit.rollbackConfirmTitle': 'Restore {item} to version {version}?', + 'engine.edit.reset': 'Reset overlay', + // objectui#13333 — the reset / delete confirm is an in-app dialog whose title + // names the item by its type label and its own label, and whose body names + // the machine `type/name` the DELETE addresses. + 'engine.edit.resetConfirm': 'Reset the overlay on {typeLabel} "{item}"?', + 'engine.edit.resetConfirmBody': 'Your customization of {type}/{name} is removed and its code-defined version applies again.', + 'engine.edit.delete': 'Delete', + 'engine.edit.deleteConfirm': 'Delete {typeLabel} "{item}"?', + 'engine.edit.deleteConfirmBody': '{type}/{name} is deleted. This cannot be undone.', + 'engine.edit.lockFull': 'This item is locked and cannot be edited or deleted.', + 'engine.edit.lockNoOverlay': 'This item is locked and cannot be edited.', + 'engine.edit.lockNoDelete': 'This item is locked and cannot be deleted.', + // objectui#5024 — the headline for a lock state this console has no + // sentence for. Reachable without any change here: `layered()` casts the + // wire value through unchecked, so a newer server can send a fifth state + // today. Names the raw token because the operator who sees it is the only + // one who can report which state their server sent. + 'engine.edit.lockUnknown': + 'This item is locked, but this console does not recognise the lock state \u2018{state}\u2019 — it may come from a newer server. Some operations will be blocked.', + 'engine.edit.history': 'History', + 'engine.edit.auditTab': 'Audit log', + 'engine.edit.auditCount': 'events', + 'engine.edit.auditEmptyTitle': 'No audit events yet', + 'engine.edit.auditEmptyDescription': 'No save, publish, rollback, delete or reset attempts have been recorded for this item. Once someone tries to change it (allowed or denied), the attempt will appear here.', + // …and the FAILED read (objectui#5169). The empty copy above is a POSITIVE + // CLAIM about the record — true for a read that completed and found nothing, + // a lie for one that never completed — and it used to render next to the + // failure banner and a measured `0 events`. This copy states only that the + // read did not complete and makes no claim in either direction about whether + // attempts were recorded. Any reword must keep that property; "no attempts + // have been recorded" is exactly the sentence this state exists to avoid. + 'engine.edit.auditErrorTitle': 'Audit trail could not be read', + 'engine.edit.auditErrorDescription': + 'The audit read did not complete, so whether anything has been recorded for this item is unknown. This is not a statement that no attempts were made — use Refresh to ask again.', + 'engine.edit.auditColTime': 'Time', + 'engine.edit.auditColActor': 'Actor', + 'engine.edit.auditColOperation': 'Operation', + 'engine.edit.auditColOutcome': 'Outcome', + 'engine.edit.auditColLock': 'Lock', + 'engine.edit.auditColNote': 'Note', + 'engine.edit.refresh': 'Refresh', + 'engine.edit.layers': 'Layers', + 'engine.edit.overlaidCount': '{count} overlaid', + 'engine.edit.noOverlay': 'no overlay', + 'engine.edit.references': 'References', + 'engine.edit.form': 'Form', + 'engine.edit.edit': 'Edit', + 'engine.edit.editOverlay': 'Edit overlay', + 'engine.edit.createNew': 'Create new', + 'engine.edit.detail': 'Detail', + 'engine.edit.preview': 'Preview', + 'engine.edit.designer': 'Designer', + 'engine.edit.related': 'Related', + 'engine.edit.hideInspector': 'Hide inspector', + 'engine.edit.showInspector': 'Show inspector', + 'engine.edit.noChanges': 'No changes to save', + 'engine.edit.inspector': 'Inspector', + 'engine.edit.inspector.properties': 'Properties', + 'engine.edit.inspector.source': 'Source', + 'engine.edit.fullscreen': 'Fullscreen', + 'engine.edit.exitFullscreen': 'Exit fullscreen', + 'engine.edit.designMode': 'Design', + 'engine.edit.previewMode': 'Preview', + // 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)', + // Flow edge (connection) inspector + 'engine.inspector.flowEdge.kind': 'Connection', + 'engine.inspector.flowEdge.close': 'Close connection', + 'engine.inspector.flowEdge.missing': 'This connection no longer exists.', + 'engine.inspector.flowEdge.source': 'From', + 'engine.inspector.flowEdge.target': 'To', + 'engine.inspector.flowEdge.routing': 'Routing', + 'engine.inspector.flowEdge.branch': 'Branch', + 'engine.inspector.flowEdge.label': 'Branch label', + 'engine.inspector.flowEdge.labelHint': 'e.g. approve / reject', + 'engine.inspector.flowEdge.condition': 'Condition', + 'engine.inspector.flowEdge.conditionHint': 'CEL expression — taken when it evaluates true', + 'engine.inspector.flowEdge.isDefault': 'Default branch (else)', + 'engine.inspector.flowEdge.hint': 'The engine follows this connection when its branch label is selected or its condition is met. The default branch is taken when no other matches.', + 'engine.inspector.flowEdge.remove': 'Remove connection', + 'engine.inspector.flowEdge.approvalBranch': 'Approval branch', + 'engine.inspector.flowEdge.branchApprove': 'Approve', + 'engine.inspector.flowEdge.branchReject': 'Reject', + 'engine.inspector.flowEdge.branchRevise': 'Revise — send back', + 'engine.inspector.flowEdge.branchCustom': '— Custom —', + // objectui#10748 — the word after ` · ` on a decision branch whose condition + // is empty or `true` (the default branch), in the branch picker. + 'engine.inspector.flowEdge.branchDefault': 'default', + 'engine.inspector.flowEdge.connection': 'Connection', + 'engine.inspector.flowEdge.type': 'Type', + 'engine.inspector.flowEdge.typeDefault': 'Normal', + 'engine.inspector.flowEdge.typeConditional': 'Conditional', + 'engine.inspector.flowEdge.typeFault': 'Fault (error path)', + 'engine.inspector.flowEdge.typeBack': 'Back-edge (revise loop)', + 'engine.inspector.flowEdge.backHint': 'A back-edge re-enters an earlier node to close a loop (e.g. an approval revise loop). It is traversed normally at run time but excluded from cycle validation.', + // Workflow action inspector + 'engine.inspector.workflowAction.kind': 'Action', + 'engine.inspector.workflowAction.close': 'Close action', + 'engine.inspector.workflowAction.type': 'Action Type', + 'engine.inspector.workflowAction.name': 'Name', + 'engine.inspector.workflowAction.config': 'Config (JSON)', + 'engine.inspector.workflowAction.remove': 'Remove action', + // View column inspector + 'engine.inspector.viewColumn.kind': 'Column', + 'engine.inspector.viewColumn.close': 'Close column', + 'engine.inspector.viewColumn.header': 'Header', + 'engine.inspector.viewColumn.accessorKey': 'Field key', + 'engine.inspector.viewColumn.width': 'Width', + 'engine.inspector.viewColumn.align': 'Align', + 'engine.inspector.viewColumn.sortable': 'Sortable', + 'engine.inspector.viewColumn.filterable': 'Filterable', + 'engine.inspector.viewColumn.remove': 'Remove column', + 'engine.inspector.viewColumn.outlineLabel': 'Columns', + // View variant inspector (the View "home" panel — also hosted by the + // runtime ObjectView's right-rail view editor) + 'engine.inspector.view.kind': 'View', + 'engine.inspector.view.close': 'Close', + 'engine.inspector.view.label': 'Label', + 'engine.inspector.view.labelPlaceholder': 'e.g. All Leads', + 'engine.inspector.view.type': 'View type', + 'engine.inspector.view.object': 'Object', + 'engine.inspector.view.noSchema': 'Spec schema unavailable — basic properties only.', + // objectui#6900 — an object-required field inside a predicate-gated form + // section (ruling 5749269225): name both facts, then the three remedies. + 'engine.inspector.view.gatedRequired.title': 'Save is blocked: a required field sits in a conditional section.', + 'engine.inspector.view.gatedRequired.issue': 'Field "{fieldLabel}" ({field}) is required on object "{object}", but it sits in section "{section}", which is shown only when {predicate}. The server never evaluates that condition, so whenever the section is hidden the form cannot be completed — the record save is refused over a field the user cannot see.', + 'engine.inspector.view.gatedRequired.remedies': 'Fix it one of three ways:', + 'engine.inspector.view.gatedRequired.remedyMove': 'Move "{fieldLabel}" into a section that has no visibleWhen.', + 'engine.inspector.view.gatedRequired.remedyDrop': 'Remove visibleWhen from section "{section}".', + 'engine.inspector.view.gatedRequired.remedyUnrequire': 'Make "{fieldLabel}" not required on object "{object}".', + // Conditional formatting editor (list/grid views) + 'engine.inspector.view.cf.title': 'Conditional formatting', + 'engine.inspector.view.cf.add': 'Add rule', + 'engine.inspector.view.cf.empty': 'No rules. Add one to color rows by a CEL condition.', + 'engine.inspector.view.cf.rule': 'Rule', + 'engine.inspector.view.cf.when': 'When (CEL)', + 'engine.inspector.view.cf.background': 'Background', + 'engine.inspector.view.cf.text': 'Text', + 'engine.inspector.view.cf.border': 'Border', + 'engine.inspector.view.cf.preview': 'Preview', + 'engine.inspector.view.cf.remove': 'Remove rule', + 'engine.inspector.view.cf.moveUp': 'Move up', + 'engine.inspector.view.cf.moveDown': 'Move down', + // ConditionBuilder row mode (objectui#10586). `≥` / `≤` stay symbols. + 'engine.inspector.condition.builder': 'Builder', + 'engine.inspector.condition.expression': 'Expression', + 'engine.inspector.condition.always': 'Always — no condition.', + 'engine.inspector.condition.and': 'AND', + 'engine.inspector.condition.or': 'OR', + 'engine.inspector.condition.subjectPlaceholder': 'field / context', + 'engine.inspector.condition.remove': 'Remove condition', + 'engine.inspector.condition.valuePlaceholder': 'value', + 'engine.inspector.condition.add': 'Add condition', + 'engine.inspector.condition.op.equals': 'equals', + 'engine.inspector.condition.op.notEquals': 'not equals', + 'engine.inspector.condition.op.greaterThan': 'greater than', + 'engine.inspector.condition.op.lessThan': 'less than', + 'engine.inspector.condition.op.truthy': 'is set / true', + 'engine.inspector.condition.op.falsy': 'is empty / false', + // objectui#11894 — the value-less pair, worded by the subject's declared + // type; the two rows above stay for a subject the catalog does not type. + 'engine.inspector.condition.op.isTrue': 'is true', + 'engine.inspector.condition.op.isFalse': 'is false', + 'engine.inspector.condition.op.isNotEmpty': 'is not empty', + 'engine.inspector.condition.op.isEmpty': 'is empty', + // objectui#10748 — the lookup filter's operator words the four above do not + // cover (`ObjectFieldInspector` `LOOKUP_OPERATORS`, which reads the four as + // well; the symbol a label leads with stays outside the word). + 'engine.inspector.condition.op.atLeast': 'at least', + 'engine.inspector.condition.op.atMost': 'at most', + 'engine.inspector.condition.op.contains': 'contains', + 'engine.inspector.condition.op.inAnyOf': 'in (any of)', + 'engine.inspector.condition.op.notIn': 'not in', + 'engine.inspector.view.type.grid': 'Table / List', + 'engine.inspector.view.type.kanban': 'Kanban', + 'engine.inspector.view.type.calendar': 'Calendar', + 'engine.inspector.view.type.gallery': 'Gallery', + 'engine.inspector.view.type.gantt': 'Gantt', + 'engine.inspector.view.type.timeline': 'Timeline', + 'engine.inspector.view.type.map': 'Map', + 'engine.inspector.view.type.chart': 'Chart', + // Report default ("home") inspector + 'engine.inspector.report.kind': 'Report', + 'engine.inspector.report.close': 'Close report', + 'engine.inspector.report.name': 'Name', + 'engine.inspector.report.nameHint': 'snake_case identifier (cannot change after create)', + 'engine.inspector.report.namePlaceholder': 'e.g. pipeline_by_stage', + 'engine.inspector.report.label': 'Label', + 'engine.inspector.report.labelPlaceholder': 'e.g. Pipeline by Stage', + 'engine.inspector.report.type': 'Report type', + 'engine.inspector.report.type.tabular': 'Tabular', + 'engine.inspector.report.type.summary': 'Summary', + 'engine.inspector.report.type.matrix': 'Matrix', + 'engine.inspector.report.type.joined': 'Joined', + 'engine.inspector.report.dataset': 'Dataset', + 'engine.inspector.report.datasetPlaceholder': 'e.g. sales_metrics', + 'engine.inspector.report.values': 'Values (measures)', + 'engine.inspector.report.valuesEmpty': 'No measures yet. Add one from the dataset below.', + 'engine.inspector.report.rows': 'Rows (dimensions)', + 'engine.inspector.report.rowsEmpty': 'No dimensions yet. Add one from the dataset below.', + 'engine.inspector.report.columnsAcross': 'Columns (across dimensions)', + 'engine.inspector.report.columnsAcrossEmpty': 'No across dimensions yet — the matrix pivots rows × columns.', + 'engine.inspector.report.chart': 'Chart', + 'engine.inspector.report.chartType': 'Chart type', + 'engine.inspector.report.chartNone': 'None (table only)', + 'engine.inspector.report.chartTitle': 'Chart title', + 'engine.inspector.report.chartX': 'X-Axis (dimension)', + 'engine.inspector.report.chartY': 'Y-Axis (measure)', + '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', + '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. + 'engine.inspector.hook.bodyLang.expression': 'Expression (L1)', + 'engine.inspector.hook.bodyLang.js': 'Sandboxed JS (L2)', + 'engine.inspector.hook.basics': 'Basics', + 'engine.inspector.hook.label': 'Label', + 'engine.inspector.hook.labelPlaceholder': 'Human-readable name', + 'engine.inspector.hook.name': 'Name', + 'engine.inspector.hook.namePlaceholder': 'snake_case identifier', + 'engine.inspector.hook.objects': 'Object(s) this hook fires on', + 'engine.inspector.hook.allObjects': 'All objects (*)', + 'engine.inspector.hook.noObjects': 'No objects found — publish an object, then pick it here.', + 'engine.inspector.hook.pickObject': 'Pick at least one object (or All objects).', + 'engine.inspector.hook.outsideReach': 'These belong to other packages or to the platform (sys_…). A hook on one runs on that object’s events wherever they happen, not only in this package.', + 'engine.inspector.hook.allObjectsReach': 'Runs on the events of every object — other packages’ objects and the platform’s own (sys_…) tables included.', + 'engine.inspector.hook.events': 'Events', + 'engine.inspector.hook.eventsHint': 'Which lifecycle events invoke this hook.', + 'engine.inspector.hook.eventsWrite': 'Write', + 'engine.inspector.hook.eventsQuery': 'Query', + 'engine.inspector.hook.pickEvent': 'Select at least one event.', + 'engine.inspector.hook.function': 'Function', + 'engine.inspector.hook.functionHint': 'The handler that runs when the hook fires.', + 'engine.inspector.hook.language': 'Language', + 'engine.inspector.hook.handlerBody': 'Handler body', + 'engine.inspector.hook.bodyHint.expression': 'A single L1 expression evaluated against the record / ctx.', + 'engine.inspector.hook.bodyHint.js': 'Runs in the sandbox as (ctx) => Promise.', + 'engine.inspector.hook.options': 'Options', + 'engine.inspector.hook.priority': 'Priority', + '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.', + // Dashboard default (home) inspector + 'engine.inspector.dashboard.kind': 'Dashboard', + 'engine.inspector.dashboard.close': 'Close dashboard', + 'engine.inspector.dashboard.label': 'Label', + 'engine.inspector.dashboard.labelPlaceholder': 'e.g. Sales Overview', + 'engine.inspector.dashboard.description': 'Description', + 'engine.inspector.dashboard.descriptionPlaceholder': 'Optional summary shown in the header', + 'engine.inspector.dashboard.widgets': 'Widgets', + '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.', + // Flow reference picker — ReferenceCombobox / FlowReferenceField (objectui#10586) + 'engine.inspector.reference.pickFromRecords': 'Pick from records', + 'engine.inspector.reference.pickFromRegistry': 'Pick from declared items', + 'engine.inspector.reference.enterManually': 'Enter value manually', + 'engine.inspector.reference.managerPlaceholder': 'Resolved automatically', + 'engine.inspector.reference.managerHint': "Resolved at runtime from the submitter's manager — no value needed.", + 'engine.inspector.reference.queueHint': "The queue's machine name. Its members when the step opens are the approvers; an empty queue adds nobody.", + 'engine.inspector.reference.fieldsOf': 'Fields of {object}.', + 'engine.inspector.reference.setTriggerObject': 'Set the flow’s trigger object (on the Start node) to list fields.', + 'engine.inspector.reference.actionsOf': 'Actions of {connector}.', + 'engine.inspector.reference.chooseConnector': 'Choose a Connector above to list its actions.', + 'engine.inspector.reference.declarative': 'declarative', + // objectui#11788 — the notify node's recipient picker (`FlowRecipientsField`). + 'engine.inspector.recipients.kindLabel': 'Recipient type', + 'engine.inspector.recipients.kind.field': 'Record field', + 'engine.inspector.recipients.kind.user': 'User', + 'engine.inspector.recipients.kind.team': 'Team', + 'engine.inspector.recipients.kind.email': 'Email address', + 'engine.inspector.recipients.kind.expression': 'Expression', + 'engine.inspector.recipients.kind.custom': 'Other', + 'engine.inspector.recipients.placeholder.field': 'Field of the trigger record', + 'engine.inspector.recipients.placeholder.user': 'Pick a user', + 'engine.inspector.recipients.placeholder.team': 'Pick a team', + 'engine.inspector.recipients.placeholder.email': 'name@example.com', + 'engine.inspector.recipients.placeholder.expression': 'CEL, e.g. ownerId', + 'engine.inspector.recipients.placeholder.custom': 'Saved as typed, e.g. role:admin', + 'engine.inspector.recipients.add': 'Add recipient', + 'engine.inspector.recipients.remove': 'Remove recipient', + 'engine.inspector.recipients.empty': 'No recipients yet.', + 'engine.inspector.recipients.notEmail': 'Not an email address — the messaging service would read it as a user id.', + 'engine.inspector.recipients.kind.emailField': 'Email field', + 'engine.inspector.recipients.placeholder.emailField': 'Email field of the trigger record', + 'engine.inspector.recipients.emailFieldHint': 'Mails the address in this field of the trigger record, by email only — for a person who is not a user. It is the node\u2019s only recipient.', + 'engine.inspector.recipients.emailFieldNotEmail': 'Not an email field — this recipient must name an email-typed field of the trigger record.', + 'engine.inspector.recipients.emailFieldChannels': 'This recipient is sent by email only: the node\u2019s channels must be email alone, or the node is refused.', + 'engine.inspector.recipients.emailFieldChannelsFix': 'Send by email only', + 'engine.inspector.recipients.fieldOnEmailNode': 'This node sends email: the address in this field must belong to a user, or the run fails. To mail the address itself, use the Email field type.', + // objectui#11788 — the create/update record nodes' field-value keys, picked from the target object. + 'engine.inspector.fieldMap.chooseObject': 'Choose the Object above to list its fields.', + // objectui#11783 — the shared object picker (inspectors/ObjectPicker.tsx). + 'engine.inspector.objectPicker.groupPackage': 'This package', + 'engine.inspector.objectPicker.groupOther': 'Other objects', + 'engine.inspector.objectPicker.groupSystem': 'System objects', + 'engine.inspector.objectPicker.showSystem': 'Show system objects ({count})', + 'engine.inspector.objectPicker.hideSystem': 'Hide system objects', + 'engine.inspector.objectPicker.noMatch': 'No object matches. Press Enter to keep what you typed.', + // Add affordances (used by OutlineStrip + custom previews) + 'engine.inspector.add.widget': 'Add widget', + 'engine.inspector.add.block': 'Add block', + 'engine.inspector.add.node': 'Add node', + 'engine.inspector.add.step': 'Add step', + 'engine.inspector.add.action': 'Add action', + 'engine.inspector.add.nav': 'Add nav item', + // References side panel (ResourceEditPage) empty state. + 'engine.edit.refsScanning': 'Scanning references…', + 'engine.edit.refsEmptyTitle': 'No references found', + 'engine.edit.refsEmptyDesc': 'Nothing in the metadata graph points at this item. Safe to delete.', + // #25916 — the references table's column headers. + 'engine.edit.refsColFromType': 'From type', + 'engine.edit.refsColFromName': 'From name', + 'engine.edit.refsColPath': 'Path', + // …and its FAILED state (objectui#5110), which must never be confusable with + // the empty one above: the copy states that the check did not complete and + // deliberately makes no claim — in either direction — about whether deleting + // is safe, because the question was not answered. Any wording change here + // must keep that property; "no references were found" is exactly the + // sentence this state exists to avoid. + 'engine.edit.refsErrorTitle': 'Reference check failed', + 'engine.edit.refsErrorDesc': + 'The reference scan did not complete, so what depends on this item is unknown. This is not a statement that deleting it is safe — run the check again before you decide.', + 'engine.edit.refsRetry': 'Retry check', + // Destructive-change (force save) dialog (ResourceEditPage). + 'engine.edit.destructiveTitle': 'Destructive change detected', + 'engine.edit.destructiveDesc': + 'The framework refused this save because it would drop or narrow data already in use. Review the issues and confirm to override.', + 'engine.edit.forcing': 'Forcing…', + // History page / panel (ResourceHistoryPage). + 'engine.edit.historySubtitle': 'Version history', + 'engine.edit.historyEvents': 'Events', + 'engine.edit.historyEmptyTitle': 'No history yet', + 'engine.edit.historyEmptyDesc': + 'This item has never been edited via an overlay. The first save will create the initial history record.', + // Reorder buttons (used in InspectorShell header) + 'engine.inspector.reorder.up': 'Move up', + 'engine.inspector.reorder.down': 'Move down', + // InspectorComboField defaults (objectui#10586). `{value}` is the typed + // text, drawn in a code span by the combo. + 'engine.inspector.combo.search': 'Search or type…', + 'engine.inspector.combo.noMatch': 'No match — keep typing to use a custom value.', + 'engine.inspector.combo.loading': 'Loading…', + 'engine.inspector.combo.useCustom': 'Use {value}', + 'engine.edit.overlay': 'overlay', + 'engine.edit.readOnlyBanner': 'Viewing in read-only mode. Click {edit} to make changes.', + // objectui#12141 — `{flag}` renders `OS_METADATA_WRITABLE`, the one spelling + // the server reads. The sentence used to offer a second remedy, a registry + // flip of the type's overlay flag, and nobody can perform it: the registry is + // a TypeScript literal in `@objectstack/spec` that no deployment edits. The + // hatch is named with its reach (ADR-0131 D6): it opens the type's items that + // no package ships, and never an item a package ships. + 'engine.edit.readOnlyTypeBanner': + 'This metadata type is read-only for safety: it ships executable code or sensitive configuration and cannot be overlaid at runtime. Edit the source in your package and redeploy. An operator can set {flag} to include {type} (use with caution); that opens only the items of this type that no package ships, never an item a package ships.', + 'engine.edit.artifactLockedBanner': + 'This {type} is provided by an installed package, so it is read-only at runtime. To change it, edit it in its source package and republish — or create a new {type} from scratch.', + 'engine.edit.readOnly': 'Read-only (runtime overrides disabled).', + 'engine.edit.loading': 'Loading', + 'engine.edit.bespokeDesigner': 'Designer', + 'engine.edit.readOnlyHint': + 'Read-only — this metadata type does not allow runtime overrides. Edit the source in the package and redeploy.', + 'engine.edit.unsaved': 'Unsaved', + 'engine.edit.unsavedHint': 'You have unsaved changes.', + 'engine.edit.diagnostics.title': + 'This metadata does not match the spec — {count} validation error(s).', + 'engine.edit.diagnostics.warnTitle': '{count} warning(s) — review recommended.', + 'engine.edit.diagnostics.more': '+{count} more…', + 'engine.edit.loadFailed': 'Failed to load {type}/{name}: {message}', + 'engine.edit.unsavedLeaveConfirm': + 'You have unsaved changes. Leave this page anyway?', + 'engine.edit.saving': 'Saving…', + 'engine.edit.savedAt': 'Saved {time}', + 'engine.edit.autoSaveOn': 'Auto-save is on — click to disable', + 'engine.edit.autoSaveOff': 'Auto-save is off — click to enable', + 'engine.edit.autoSavingShortly': 'Auto-saving…', + 'engine.edit.destructive': 'Destructive change', + 'engine.edit.destructiveHint': + 'The change would break existing references. Review the issues and confirm to force-save.', + 'engine.edit.forceSave': 'Force save', + 'engine.form.selectEllipsis': 'Select…', + 'engine.form.add': 'Add', + 'engine.form.addItem': 'Add item', + 'engine.form.addRow': 'Add row', + 'engine.form.remove': 'Remove', + 'engine.form.removeRow': 'Remove row', + 'engine.form.dragToReorder': 'Drag to reorder', + 'engine.form.arrayPlaceholder': 'comma, separated, values', + // Accessible name for the hex mirror beside the native colour picker. The + // picker itself is named by the field's visible label (objectui#4871); this + // second input edits the same value and needs a name of its own. + 'engine.form.colorHex': 'Hex value', + 'engine.form.loadingObjects': 'Loading objects…', + 'engine.form.noObjects': 'object_name (no objects detected)', + 'engine.form.selectObject': 'Select object…', + 'engine.form.selectComponent': 'Select component…', + 'engine.form.noComponents': 'component_id (no components on this page yet)', + 'engine.form.addObjects': 'Add objects…', + 'engine.form.loadingFields': 'Loading fields…', + 'engine.form.selectObjectFirst': '(Select an object first)', + 'engine.form.selectField': 'Select field…', + 'engine.form.addFields': 'Add fields…', + 'engine.form.noObjectBound': 'No object bound', + // The FAILURE state shared by every option picker (objectui#5170). It must + // never be confusable with the empty-state copy around it — `noObjects` + // ("no objects detected") is a MEASUREMENT and `noObjectBound` names a CAUSE, + // and on a failed load both are false. This copy states only that the list + // could not be loaded and makes no claim in either direction about whether + // options exist, because the question was not answered. Any reword must keep + // that property: "no options were found" is exactly the sentence this state + // exists to avoid. + 'engine.form.loadingOptions': 'Loading options…', + 'engine.form.optionsLoadFailedTitle': 'Options could not be loaded', + 'engine.form.optionsLoadFailedDesc': + 'This list did not load, so it is not a statement that no options exist. Reload the editor to ask again.', + 'engine.form.none': '— None —', + 'engine.form.notInObject': '(not in object)', + // The flags a designer picker puts on a STORED value its roster does not + // offer (objectui#9652). Each is a bare suffix, like `notInObject` above, + // which `widgets.tsx` draws in a muted span of its own. A picker that shows + // the value and its flag as ONE option label joins them through + // `flaggedValue`, so the order of the two and the gap between them belong to + // the locale rather than to the call site: zh sets no space before the + // full-width bracket its flags open with. + 'engine.form.notFound': '(not found)', + 'engine.form.deprecated': '(deprecated)', + // objectui#10448: a stored tier outside the org-membership-level enum, and a + // hook's selected object that the live catalog does not list. + 'engine.form.invalid': '(invalid)', + 'engine.form.notPublished': '(not published)', + 'engine.form.flaggedValue': '{value} {flag}', + 'engine.form.searchIcons': 'Search icons…', + 'engine.form.chooseIcon': 'Choose an icon', + 'engine.form.iconsTruncated': 'Showing the first {shown} of {total} — type to narrow.', + 'engine.form.noMatchingIcons': 'No matching icons.', + // objectui#12531 — the icon picker's inline warning on a stored name that + // matches no icon in the catalog it offers. `{name}` is the stored value. + 'engine.form.unknownIcon': 'No icon is named “{name}”. Choose one from the list.', + 'engine.form.keep': 'Keep', + 'engine.form.addField': 'Add field…', + 'engine.form.addFieldPlain': 'Add field', + 'engine.form.searchFields': 'Search fields…', + 'engine.form.allFieldsAdded': 'All fields added', + // objectui#12126 — the `ref-multi:permission` picker's add control: its + // accessible name, its placeholder, and the placeholder once every declared + // permission set is picked. + 'engine.form.addPermissionSetPlain': 'Add permission set', + 'engine.form.addPermissionSet': 'Add permission set…', + 'engine.form.allPermissionSetsAdded': 'All permission sets added', + 'engine.form.noObjectFields': 'No object fields ({error}).', + // objectui#10862, slice 4 — the not-found `error` the field and dataset + // catalogue hooks (`useObjectFields`, `useDatasetSemantics`) publish, read + // in the designer locale where the hook returns it. Every other `error` + // they carry is the transport's own message and passes through. + 'engine.form.objectNotFound': 'Object not found', + 'engine.form.datasetNotFound': 'Dataset not found', + 'engine.form.noMatchingFields': 'No matching fields.', + 'engine.form.noFieldsOnObject': 'No fields on this object.', + 'engine.form.added': 'Added', + 'engine.form.tagsPlaceholder': 'Type and press Enter…', + 'engine.form.noRows': 'No rows. Click + to add.', + 'engine.form.masterDetailSchemaError': 'master-detail widget requires items.properties on the JSON schema (or an anyOf branch that has them).', + 'engine.form.missingField': 'Field {field} declared in form layout but missing from schema. Skipping.', + 'engine.form.fallbackJson': 'widget {widget} — falling back to JSON until a custom renderer is registered.', + 'engine.form.keyRequired': 'Key is required', + 'engine.form.keyExists': 'Key "{key}" already exists', + 'engine.form.keyPattern': 'Key must match {pattern}', + 'engine.form.invalidJson': 'Invalid JSON', + 'engine.form.rootJsonObject': 'Root must be a JSON object', + // objectui#8218 — the metadata-admin form is now the Studio 「界面」 property + // panel an AI-build maker edits, so every literal it still printed in English + // landed inside an otherwise-Chinese surface. Swept from `widgets.tsx` + + // `SchemaForm.tsx`; keys below are the ones that sweep introduced. + 'engine.form.machineName': 'Machine name', + 'engine.form.sectionN': 'Section {n}', + 'engine.form.notConfigured': 'Not configured', + 'engine.form.configure': 'Configure', + 'engine.form.removeNamed': 'Remove {name}', + 'engine.form.moveUp': 'Move up', + 'engine.form.moveDown': 'Move down', + 'engine.form.noneShort': 'none', + 'engine.form.filterModeNone': 'None', + 'engine.form.filterModeTabs': 'Tabs', + 'engine.form.filterModeDropdown': 'Dropdown', + 'engine.form.filterElement': 'Filter element', + 'engine.form.removeField': 'Remove field', + 'engine.form.addFilterField': '+ Add filter field…', + 'engine.form.bindObjectForFilterFields': 'Bind a source object to pick filter fields.', + 'engine.form.tabLabel': 'Tab label', + 'engine.form.moveTabUp': 'Move tab up', + 'engine.form.moveTabDown': 'Move tab down', + 'engine.form.removeTab': 'Remove tab', + 'engine.form.bindObjectForTabRules': 'Bind a source object to build tab filter rules.', + 'engine.form.bindObjectForConditions': 'Bind a source object to add filter conditions.', + 'engine.form.addActionButtonPlain': 'Add action button', + 'engine.form.addActionButton': '+ Add action button…', + 'engine.form.allActionsAdded': 'All actions added', + 'engine.form.bindObjectForActions': 'Bind a source object to pick actions', + 'engine.form.color': 'Color', + // A notation, not prose — but it still reaches the reader, so it goes + // through the table like everything else rather than being hard-coded. A + // locale that writes hex differently now has somewhere to say so. + 'engine.form.colorHexPlaceholder': '#RRGGBB', + 'engine.form.secretSetTypeToReplace': '•••••••• set — type to replace', + 'engine.form.secretSetLeaveBlank': '•••••••• set — leave blank to keep', + 'engine.form.enterAValue': 'Enter a value', + 'engine.form.secretValue': 'Secret value', + 'engine.form.hideValue': 'Hide value', + 'engine.form.revealValue': 'Reveal value', + 'engine.form.clear': 'Clear', + 'engine.form.anOption': 'an option', + 'engine.form.selectDepToConfigure': 'Select {dep} to configure.', + 'engine.form.noConfigNeeded': 'No configuration needed for "{value}".', + 'engine.form.readOnly': 'read-only', + 'engine.form.loadingEditor': 'Loading editor…', + 'engine.packages.title': 'Packages', + 'engine.packages.description': 'Author, publish, and manage the packages installed in this system.', + 'engine.packages.refresh': 'Refresh', + 'engine.packages.import': 'Import', + 'engine.packages.importing': 'Importing…', + 'engine.packages.new': 'New Package', + 'engine.packages.moreActions': 'More actions', + 'engine.packages.search': 'Search packages…', + 'engine.packages.showPlatform': 'Show platform packages ({count})', + 'engine.packages.empty': 'No packages', + 'engine.packages.emptyCreate': 'Create your first package to start authoring metadata.', + 'engine.packages.emptyFiltered': 'No packages match your filters.', + 'engine.packages.installedApps.title': 'Installed Apps', + 'engine.packages.installedApps.empty': 'No apps installed from the marketplace yet', + 'engine.packages.installedApps.emptyHint': 'Apps you install from the marketplace are listed here.', + 'engine.packages.loadFailed': 'Failed to load packages', + 'engine.packages.col.name': 'Name', + 'engine.packages.col.version': 'Version', + 'engine.packages.col.scope': 'Scope', + 'engine.packages.col.status': 'Status', + 'engine.packages.scope.project': 'Read-only · code', + 'engine.packages.scope.system': 'System', + 'engine.packages.scope.cloud': 'Cloud', + 'engine.packages.scope.writable': 'Writable', + 'engine.packages.scope.readOnly': 'Read-only', + 'engine.packages.scope.unknown': 'Package "{id}" was refused: the server did not say whether it is writable.', + 'engine.packages.col.source': 'Source', + 'engine.packages.col.actions': 'Actions', + 'engine.packages.source.code': 'Code', + 'engine.packages.source.marketplace': 'Marketplace', + 'engine.packages.source.authored': 'Authored', + 'engine.packages.source.showAll': 'Show all', + 'engine.packages.source.unknown': 'Package "{id}" was refused: the server sent no source for it, or one that is not code, marketplace or authored.', + 'engine.packages.source.filterUnknown': 'Unknown source filter "{value}". Use code, marketplace or authored.', + 'engine.packages.source.filterRepeated': 'The source filter is given more than once ({value}). Give one of code, marketplace or authored.', + 'engine.packages.status.enabled': 'Enabled', + 'engine.packages.status.disabled': 'Disabled', + 'engine.packages.import.invalidJson': 'Selected file is not valid JSON.', + 'engine.packages.import.invalidPackage': 'Invalid package file: missing "id" or "name".', + 'engine.packages.import.success': 'Imported "{id}". Refresh the app switcher to use it.', + 'engine.packages.import.failed': 'Import failed', + 'engine.packages.create.title': 'New Package', + 'engine.packages.create.description': 'Create a writable package to author and publish your own metadata. The id should be reverse-domain (e.g. {example}).', + 'engine.packages.create.id': 'Package ID', + 'engine.packages.create.namespace': 'Object namespace', + 'engine.packages.create.name': 'Display name', + // objectui#11792 — the create dialog's Package ID field (PackageIdInput), + // which judges by the spec's id rule rather than the landing form's own. + 'engine.packages.idRule.formatHint': 'Use a reverse-domain id like {example}: two or more parts separated by dots, each made of lowercase letters, digits and inner hyphens. Underscores are not allowed.', + 'engine.packages.idRule.strippedNotice': 'Unsupported characters were removed — allowed: lowercase letters, digits, dots and hyphens', + 'engine.packages.idRule.derived': 'Filled in from the display name until you edit it.', + 'engine.packages.idRule.notDerived': 'The display name has no Latin letters to make an ID from. Type one, like {example}.', + 'engine.packages.nsRule.derived': 'Filled in from the package ID until you edit it.', + 'engine.packages.nsRule.required': 'Required: every object name in this package starts with it.', + 'engine.packages.versionRule.default': 'A starting version, kept until you edit it.', + 'engine.packages.create.version': 'Version', + 'engine.packages.create.versionInvalid': 'Use semantic version, e.g. 0.1.0', + 'engine.packages.create.creating': 'Creating…', + 'engine.packages.create.submit': 'Create package', + 'engine.packages.create.failed': 'Failed to create package', + 'engine.packages.create.exists': 'A package with this id already exists.', + // objectstack#8270 — the server refuses package writes without the ADR-0066 + // `manage_metadata` capability, and its refusal is an English sentence that + // reached a zh console verbatim. Deployments that withhold the capability do + // so deliberately, so this states the posture rather than inviting a retry. + 'engine.packages.noCapability': 'You do not have permission to manage packages in this system — it requires the “Manage Metadata” permission.', + 'engine.packages.form.basics': 'Basics', + 'engine.packages.form.advanced': 'Advanced', + 'engine.packages.form.namespace': 'Namespace', + 'engine.packages.form.defaultDatasource': 'Default datasource', + 'engine.packages.form.scope': 'Scope', + 'engine.packages.form.dependencies': 'Dependencies', + // objectui#13278 — the Dependencies row's key/value editor. + 'engine.packages.dependencies.package': 'Package', + 'engine.packages.dependencies.range': 'Version range', + 'engine.packages.dependencies.add': 'Add dependency', + 'engine.packages.dependencies.remove': 'Remove dependency', + 'engine.packages.dependencies.empty': 'No dependencies.', + 'engine.packages.dependencies.nameRequired': 'Enter a package name.', + 'engine.packages.dependencies.duplicate': 'This package is already listed.', + 'engine.packages.view.title': 'Package info', + 'engine.packages.detail.viewInfo': 'View info', + 'engine.packages.detail.type': 'Type', + 'engine.packages.detail.description': 'Description', + 'engine.packages.detail.browseMetadata': "Browse this package's metadata", + 'engine.packages.detail.pendingChanges': 'Pending changes', + 'engine.packages.detail.pendingHint': 'Drafted, not yet published. Publish the whole app, or review each below.', + 'engine.packages.detail.publishApp': 'Publish app ({count})', + 'engine.packages.detail.discardChanges': 'Discard changes ({count})', + 'engine.packages.detail.kernelReadOnly': 'This is a platform kernel package. Authoring actions are disabled.', + 'engine.packages.detail.actions': 'Actions', + 'engine.packages.detail.edit': 'Edit', + 'engine.packages.edit.title': 'Edit package', + 'engine.packages.edit.save': 'Save changes', + 'engine.packages.edit.saving': 'Saving…', + 'engine.packages.edit.saved': 'Package updated', + 'engine.packages.edit.failed': 'Failed to update package', + 'engine.packages.detail.publish': 'Publish', + 'engine.packages.detail.publishing': 'Publishing…', + 'engine.packages.detail.revert': 'Revert', + 'engine.packages.detail.enable': 'Enable', + 'engine.packages.detail.disable': 'Disable', + 'engine.packages.detail.export': 'Export', + 'engine.packages.detail.exporting': 'Exporting…', + 'engine.packages.detail.deleteApp': 'Delete app', + 'engine.packages.detail.deleting': 'Deleting…', + 'engine.packages.detail.discarding': 'Discarding…', + 'engine.packages.detail.actionFailed': 'Action failed', + 'engine.packages.detail.publishBlocked': 'Publish blocked by {count} validation error(s).', + 'engine.packages.detail.nothingToPublish': 'Nothing to publish.', + 'engine.packages.detail.published': 'Package published.', + 'engine.packages.detail.publishDraftsPartial': 'Published {published}; {failed} failed.', + 'engine.packages.detail.publishDraftsRolledBack': 'Nothing was published — the batch rolled back (all-or-nothing): {cause}', + 'engine.packages.detail.publishDraftsOk': 'App published — all drafts are now live.', + 'engine.packages.detail.reverted': 'Reverted to last published state.', + 'engine.packages.detail.discardDraftsPartial': 'Discarded {discarded}; {failed} failed.', + 'engine.packages.detail.discardDraftsOk': 'All pending changes discarded — back to the published version.', + 'engine.packages.detail.discardTitle': 'Discard all pending changes ({count})?', + 'engine.packages.detail.discardDescription': 'Every draft in this app is discarded and the app goes back to its last published version. Published metadata and data are not touched. Discarded drafts cannot be recovered.', + 'engine.packages.detail.deleteTitle': 'Delete "{name}"?', + 'engine.packages.detail.deleteDescription': 'This removes every object, view, dashboard and app in the package. Choose what happens to the records those objects hold. This cannot be undone.', + 'engine.packages.detail.deleteModeStructure': 'Delete the structure, keep the records', + 'engine.packages.detail.deleteModeStructureHint': "The package's metadata is removed. The database tables its objects created, and every record in them, are kept.", + 'engine.packages.detail.deleteModeAll': 'Delete the structure and the data', + 'engine.packages.detail.deleteModeAllHint': 'Also drops the database tables its objects created, with every record in them.', + 'engine.packages.detail.deleteTypeName': 'Type "{name}" to confirm', + 'engine.packages.detail.duplicateGo': 'Create copy', + 'engine.packages.detail.deleteFailed': 'Delete failed', + 'engine.packages.detail.disabled': 'Package disabled.', + 'engine.packages.detail.enabled': 'Package enabled.', + 'engine.packages.detail.exported': 'Package exported.', + 'engine.packages.detail.duplicate': 'Duplicate', + 'engine.packages.detail.duplicating': 'Duplicating…', + 'engine.packages.detail.duplicated': 'Package duplicated into a new base.', + 'engine.packages.detail.adoptOrphans': 'Adopt loose items', + 'engine.packages.detail.adopting': 'Adopting…', + 'engine.packages.detail.adoptTitle': 'Adopt loose items into "{name}"?', + 'engine.packages.detail.adoptDescription': 'Every package-less (loose) metadata item in this system moves into this base. Items that already belong to a package stay where they are.', + 'engine.packages.detail.adoptedOne': '{count} item adopted into this base.', + 'engine.packages.detail.adoptedOther': '{count} items adopted into this base.', }; 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 d2a2894511b..4ba7aa952e1 100644 --- a/ui/packages/i18n/src/namespaces/studio/zh.ts +++ b/ui/packages/i18n/src/namespaces/studio/zh.ts @@ -960,6 +960,635 @@ const STUDIO_ZH: Record = { 'engine.inspector.dataset.filterConditionsOther': '{count} 个条件', 'engine.inspector.dataset.addFilter': '+ 添加筛选…', 'engine.inspector.dataset.filterNeedsObject': '先选择基础对象,才能添加筛选条件。', + // engine.inspector (the remaining inspectors), engine.packages, engine.form + // and engine.edit (#25922 slice S3). The `engine.packages.form.help.*` rows + // are zh-only on purpose: the en text is the producer's own and is read + // through `tOptional`. + 'engine.edit.capabilityReadOnly': + '您的账号只能查看:修改此条目需要 `manage_metadata` 能力,该能力由平台管理员持有。', + 'engine.edit.save': '保存', + 'engine.edit.publish': '发布', + 'engine.edit.publishBlockedDirty': '请先保存再发布。', + 'engine.edit.discardDraft': '丢弃草稿', + 'engine.edit.discardDraftConfirm': '丢弃 {item} 的待发布草稿?', + 'engine.edit.discardDraftConfirmBody': '已发布的版本保持不变,仅移除未发布的更改。', + 'engine.edit.draftDiscarded': '已丢弃 {item} 的待发布草稿', + 'engine.edit.draftPending': '存在未发布的草稿 — 保存仅写入草稿,点击"发布"使其生效。', + 'engine.edit.rollback': '回滚到此版本', + 'engine.edit.rollbackConfirm': '恢复版本 {version}?该历史内容将被写回作为当前已发布的覆盖层。', + 'engine.edit.rollbackConfirmTitle': '将 {item} 恢复到版本 {version}?', + 'engine.edit.reset': '重置覆盖', + 'engine.edit.resetConfirm': '重置{typeLabel}“{item}”的覆盖层?', + 'engine.edit.resetConfirmBody': '将移除您对 {type}/{name} 的自定义,恢复使用代码中定义的版本。', + 'engine.edit.delete': '删除', + 'engine.edit.deleteConfirm': '删除{typeLabel}“{item}”?', + 'engine.edit.deleteConfirmBody': '{type}/{name} 将被删除。此操作不可撤销。', + 'engine.edit.lockFull': '该元数据已锁定,不可编辑或删除。', + 'engine.edit.lockNoOverlay': '该元数据已锁定,不可编辑。', + 'engine.edit.lockNoDelete': '该元数据已锁定,不可删除。', + 'engine.edit.lockUnknown': + '该元数据已锁定,但当前控制台无法识别锁状态“{state}”——它可能来自更新版本的服务端。部分操作将被阻止。', + 'engine.edit.history': '历史', + 'engine.edit.auditTab': '审计日志', + 'engine.edit.auditCount': '条记录', + 'engine.edit.auditEmptyTitle': '暂无审计记录', + 'engine.edit.auditEmptyDescription': '该元数据尚未发生任何保存、发布、回滚、删除或重置操作。一旦有人尝试修改(无论允许或拒绝),都会在此显示。', + // The zh half of the audit failure state (objectui#5169). Same rule as the en + // table above: the empty copy is a positive claim about the record, so this + // copy must state only that the read did not complete. + 'engine.edit.auditErrorTitle': '审计记录读取失败', + 'engine.edit.auditErrorDescription': + '审计读取没有完成,因此无法得知这一项是否有过记录。这并不表示没有发生过任何尝试 —— 请使用「刷新」重新读取。', + 'engine.edit.auditColTime': '时间', + 'engine.edit.auditColActor': '操作人', + 'engine.edit.auditColOperation': '操作', + 'engine.edit.auditColOutcome': '结果', + 'engine.edit.auditColLock': '锁状态', + 'engine.edit.auditColNote': '备注', + 'engine.edit.refresh': '刷新', + 'engine.edit.layers': '层次', + 'engine.edit.overlaidCount': '{count} 处覆盖', + 'engine.edit.noOverlay': '无覆盖', + 'engine.edit.references': '引用关系', + 'engine.edit.form': '表单', + 'engine.edit.edit': '编辑', + 'engine.edit.editOverlay': '编辑覆盖层', + 'engine.edit.createNew': '新建', + 'engine.edit.detail': '详情', + 'engine.edit.preview': '预览', + 'engine.edit.designer': '可视化编辑器', + 'engine.edit.related': '关联', + 'engine.edit.hideInspector': '隐藏属性面板', + 'engine.edit.showInspector': '显示属性面板', + 'engine.edit.noChanges': '没有需要保存的更改', + 'engine.edit.inspector': '属性', + 'engine.edit.inspector.properties': '属性', + 'engine.edit.inspector.source': '源码', + 'engine.edit.fullscreen': '全屏', + 'engine.edit.exitFullscreen': '退出全屏', + 'engine.edit.designMode': '设计', + 'engine.edit.previewMode': '预览', + 'engine.inspector.draftSuffix': '(草稿)', + // Flow edge (connection) inspector + 'engine.inspector.flowEdge.kind': '连线', + 'engine.inspector.flowEdge.close': '关闭连线', + 'engine.inspector.flowEdge.missing': '该连线已不存在。', + 'engine.inspector.flowEdge.source': '起点', + 'engine.inspector.flowEdge.target': '终点', + 'engine.inspector.flowEdge.routing': '路由', + 'engine.inspector.flowEdge.branch': '分支', + 'engine.inspector.flowEdge.label': '分支标签', + 'engine.inspector.flowEdge.labelHint': '例如 approve / reject', + 'engine.inspector.flowEdge.condition': '条件', + 'engine.inspector.flowEdge.conditionHint': 'CEL 表达式 —— 为真时走此连线', + 'engine.inspector.flowEdge.isDefault': '默认分支(else)', + 'engine.inspector.flowEdge.hint': '当此连线的分支标签被选中、或其条件成立时,引擎会走这条连线。其余都不匹配时走默认分支。', + 'engine.inspector.flowEdge.remove': '删除连线', + 'engine.inspector.flowEdge.approvalBranch': '审批分支', + 'engine.inspector.flowEdge.branchApprove': '通过', + 'engine.inspector.flowEdge.branchReject': '驳回', + 'engine.inspector.flowEdge.branchRevise': '退回修改', + 'engine.inspector.flowEdge.branchCustom': '—— 自定义 ——', + 'engine.inspector.flowEdge.branchDefault': '默认', + 'engine.inspector.flowEdge.connection': '连线', + 'engine.inspector.flowEdge.type': '类型', + 'engine.inspector.flowEdge.typeDefault': '普通', + 'engine.inspector.flowEdge.typeConditional': '条件', + 'engine.inspector.flowEdge.typeFault': '故障(错误路径)', + 'engine.inspector.flowEdge.typeBack': '回边(退回修改环)', + 'engine.inspector.flowEdge.backHint': '回边会重新进入更早的节点以闭合一个环(例如审批的退回修改环)。它在运行时正常通行,但不参与环路校验。', + // Workflow action inspector + 'engine.inspector.workflowAction.kind': '动作', + 'engine.inspector.workflowAction.close': '关闭动作', + 'engine.inspector.workflowAction.type': '动作类型', + 'engine.inspector.workflowAction.name': '名称', + 'engine.inspector.workflowAction.config': '配置(JSON)', + 'engine.inspector.workflowAction.remove': '删除动作', + // View column inspector + 'engine.inspector.viewColumn.kind': '列', + 'engine.inspector.viewColumn.close': '关闭列', + 'engine.inspector.viewColumn.header': '表头', + 'engine.inspector.viewColumn.accessorKey': '字段键', + 'engine.inspector.viewColumn.width': '宽度', + 'engine.inspector.viewColumn.align': '对齐', + 'engine.inspector.viewColumn.sortable': '可排序', + 'engine.inspector.viewColumn.filterable': '可过滤', + 'engine.inspector.viewColumn.remove': '删除列', + 'engine.inspector.viewColumn.outlineLabel': '列', + // View variant inspector + 'engine.inspector.view.kind': '视图', + 'engine.inspector.view.close': '关闭', + 'engine.inspector.view.label': '名称', + 'engine.inspector.view.labelPlaceholder': '例如:全部线索', + 'engine.inspector.view.type': '视图类型', + 'engine.inspector.view.object': '对象', + 'engine.inspector.view.noSchema': 'spec 模式不可用 —— 仅显示基础属性。', + // objectui#6900 — 见英文表同名条目。 + 'engine.inspector.view.gatedRequired.title': '保存已被拦截:必填字段位于条件区块中。', + 'engine.inspector.view.gatedRequired.issue': '字段“{fieldLabel}”({field})在对象“{object}”上是必填的,但它位于区块“{section}”中,而该区块仅在 {predicate} 时显示。服务端从不计算这个条件,因此区块被隐藏时表单无法完成——保存记录会被拒绝,报错指向一个用户看不到的字段。', + 'engine.inspector.view.gatedRequired.remedies': '可用以下三种方式之一修复:', + 'engine.inspector.view.gatedRequired.remedyMove': '把“{fieldLabel}”移到没有 visibleWhen 的区块中。', + 'engine.inspector.view.gatedRequired.remedyDrop': '去掉区块“{section}”上的 visibleWhen。', + 'engine.inspector.view.gatedRequired.remedyUnrequire': '在对象“{object}”上把“{fieldLabel}”改为非必填。', + // 条件格式化编辑器(列表/表格视图) + 'engine.inspector.view.cf.title': '条件格式化', + 'engine.inspector.view.cf.add': '添加规则', + 'engine.inspector.view.cf.empty': '暂无规则。添加一条以按 CEL 条件为行着色。', + 'engine.inspector.view.cf.rule': '规则', + 'engine.inspector.view.cf.when': '条件(CEL)', + 'engine.inspector.view.cf.background': '背景色', + 'engine.inspector.view.cf.text': '文字色', + 'engine.inspector.view.cf.border': '边框色', + 'engine.inspector.view.cf.preview': '预览', + 'engine.inspector.view.cf.remove': '删除规则', + 'engine.inspector.view.cf.moveUp': '上移', + 'engine.inspector.view.cf.moveDown': '下移', + // ConditionBuilder row mode (objectui#10586). The join words follow the + // packs' own `filterBuilder.and` / `filterBuilder.or`. + 'engine.inspector.condition.builder': '搭建器', + 'engine.inspector.condition.expression': '表达式', + 'engine.inspector.condition.always': '始终生效 —— 无条件。', + 'engine.inspector.condition.and': '且', + 'engine.inspector.condition.or': '或', + 'engine.inspector.condition.subjectPlaceholder': '字段 / 上下文', + 'engine.inspector.condition.remove': '移除条件', + 'engine.inspector.condition.valuePlaceholder': '值', + 'engine.inspector.condition.add': '添加条件', + 'engine.inspector.condition.op.equals': '等于', + 'engine.inspector.condition.op.notEquals': '不等于', + 'engine.inspector.condition.op.greaterThan': '大于', + 'engine.inspector.condition.op.lessThan': '小于', + 'engine.inspector.condition.op.truthy': '已设置 / 为真', + 'engine.inspector.condition.op.falsy': '为空 / 为假', + 'engine.inspector.condition.op.isTrue': '为真', + 'engine.inspector.condition.op.isFalse': '为假', + 'engine.inspector.condition.op.isNotEmpty': '不为空', + 'engine.inspector.condition.op.isEmpty': '为空', + 'engine.inspector.condition.op.atLeast': '至少', + 'engine.inspector.condition.op.atMost': '至多', + 'engine.inspector.condition.op.contains': '包含', + 'engine.inspector.condition.op.inAnyOf': '属于(任一)', + 'engine.inspector.condition.op.notIn': '不属于', + 'engine.inspector.view.type.grid': '表格 / 列表', + 'engine.inspector.view.type.kanban': '看板', + 'engine.inspector.view.type.calendar': '日历', + 'engine.inspector.view.type.gallery': '画廊', + 'engine.inspector.view.type.gantt': '甘特图', + 'engine.inspector.view.type.timeline': '时间线', + 'engine.inspector.view.type.map': '地图', + 'engine.inspector.view.type.chart': '图表', + // Report default ("home") inspector + 'engine.inspector.report.kind': '报表', + 'engine.inspector.report.close': '关闭报表', + 'engine.inspector.report.name': '名称', + 'engine.inspector.report.nameHint': 'snake_case 标识符(创建后不可修改)', + 'engine.inspector.report.namePlaceholder': '例如:pipeline_by_stage', + 'engine.inspector.report.label': '显示名', + 'engine.inspector.report.labelPlaceholder': '例如:按阶段统计的销售管道', + 'engine.inspector.report.type': '报表类型', + 'engine.inspector.report.type.tabular': '表格', + 'engine.inspector.report.type.summary': '汇总', + 'engine.inspector.report.type.matrix': '矩阵', + 'engine.inspector.report.type.joined': '联合', + 'engine.inspector.report.dataset': '数据集', + 'engine.inspector.report.datasetPlaceholder': '例如:sales_metrics', + 'engine.inspector.report.values': '值(度量)', + 'engine.inspector.report.valuesEmpty': '还没有度量。从下方数据集中添加。', + 'engine.inspector.report.rows': '行(维度)', + 'engine.inspector.report.rowsEmpty': '还没有维度。从下方数据集中添加。', + 'engine.inspector.report.columnsAcross': '列(横向维度)', + 'engine.inspector.report.columnsAcrossEmpty': '还没有横向维度——矩阵按 行 × 列 透视。', + 'engine.inspector.report.chart': '图表', + 'engine.inspector.report.chartType': '图表类型', + 'engine.inspector.report.chartNone': '无(仅表格)', + 'engine.inspector.report.chartTitle': '图表标题', + 'engine.inspector.report.chartX': 'X 轴(维度)', + 'engine.inspector.report.chartY': 'Y 轴(度量)', + 'engine.inspector.report.noSchema': '规格 schema 不可用 —— 仅显示基础属性。', + // Trailing section for fields the live server has but the bundled spec lacks. + 'engine.inspector.moreFields': '更多字段', + 'engine.inspector.hook.kind': '钩子', + // Hook default (home) inspector (objectui#10586). + 'engine.inspector.hook.bodyLang.expression': '表达式(L1)', + 'engine.inspector.hook.bodyLang.js': '沙箱 JS(L2)', + 'engine.inspector.hook.basics': '基本信息', + 'engine.inspector.hook.label': '显示名', + 'engine.inspector.hook.labelPlaceholder': '便于阅读的名称', + 'engine.inspector.hook.name': '名称', + 'engine.inspector.hook.namePlaceholder': 'snake_case 标识符', + 'engine.inspector.hook.objects': '此钩子触发的对象', + 'engine.inspector.hook.allObjects': '所有对象(*)', + 'engine.inspector.hook.noObjects': '未找到对象 —— 请先发布一个对象,再在此处选择。', + 'engine.inspector.hook.pickObject': '请至少选择一个对象(或「所有对象」)。', + 'engine.inspector.hook.outsideReach': '这些对象属于其他软件包或平台(sys_…)。钩子挂在其中某个对象上时,该对象的事件无论在哪里发生都会触发它,而不只是在本软件包内。', + 'engine.inspector.hook.allObjectsReach': '会在每个对象的事件上运行 —— 包括其他软件包的对象和平台自身的(sys_…)表。', + 'engine.inspector.hook.events': '事件', + 'engine.inspector.hook.eventsHint': '哪些生命周期事件会调用此钩子。', + 'engine.inspector.hook.eventsWrite': '写入', + 'engine.inspector.hook.eventsQuery': '查询', + 'engine.inspector.hook.pickEvent': '请至少选择一个事件。', + 'engine.inspector.hook.function': '函数', + 'engine.inspector.hook.functionHint': '钩子触发时运行的处理函数。', + 'engine.inspector.hook.language': '语言', + 'engine.inspector.hook.handlerBody': '处理函数主体', + 'engine.inspector.hook.bodyHint.expression': '针对 record / ctx 求值的单个 L1 表达式。', + 'engine.inspector.hook.bodyHint.js': '在沙箱中以 (ctx) => Promise 的形式运行。', + 'engine.inspector.hook.options': '选项', + 'engine.inspector.hook.priority': '优先级', + 'engine.inspector.hook.async': '异步运行(提交之后)', + 'engine.inspector.hook.condition': '仅在以下条件成立时运行(可选 CEL)', + 'engine.inspector.hook.moreFieldsHint': '高级 / 不常用的属性。', + // Dashboard default (home) inspector + 'engine.inspector.dashboard.kind': '仪表盘', + 'engine.inspector.dashboard.close': '关闭仪表盘', + 'engine.inspector.dashboard.label': '显示名', + 'engine.inspector.dashboard.labelPlaceholder': '例如:销售总览', + 'engine.inspector.dashboard.description': '描述', + 'engine.inspector.dashboard.descriptionPlaceholder': '可选,显示在头部的摘要', + 'engine.inspector.dashboard.widgets': '组件', + 'engine.inspector.dashboard.widgetsEmpty': '还没有组件。在下方添加。', + 'engine.inspector.dashboard.removeWidget': '删除组件', + 'engine.inspector.dashboard.noSchema': '规格 schema 不可用 —— 仅显示基础属性。', + // Flow reference picker — ReferenceCombobox / FlowReferenceField (objectui#10586) + 'engine.inspector.reference.pickFromRecords': '从记录中选择', + 'engine.inspector.reference.pickFromRegistry': '从已声明的项中选择', + 'engine.inspector.reference.enterManually': '手动输入值', + 'engine.inspector.reference.managerPlaceholder': '自动解析', + 'engine.inspector.reference.managerHint': '运行时根据提交人的上级自动解析 —— 无需填写。', + 'engine.inspector.reference.queueHint': '队列的机器名。步骤开启时队列的成员即为审批人;空队列不会添加任何人。', + 'engine.inspector.reference.fieldsOf': '{object} 的字段。', + 'engine.inspector.reference.setTriggerObject': '请先在开始节点上设置流程的触发对象,才能列出字段。', + 'engine.inspector.reference.actionsOf': '{connector} 的动作。', + 'engine.inspector.reference.chooseConnector': '请先在上方选择连接器,才能列出其动作。', + 'engine.inspector.reference.declarative': '声明式', + // objectui#11788 — 通知节点的收件人选择器(`FlowRecipientsField`)。 + 'engine.inspector.recipients.kindLabel': '收件人类型', + 'engine.inspector.recipients.kind.field': '记录字段', + 'engine.inspector.recipients.kind.user': '用户', + 'engine.inspector.recipients.kind.team': '团队', + 'engine.inspector.recipients.kind.email': '邮箱地址', + 'engine.inspector.recipients.kind.expression': '表达式', + 'engine.inspector.recipients.kind.custom': '其他', + 'engine.inspector.recipients.placeholder.field': '触发记录的字段', + 'engine.inspector.recipients.placeholder.user': '选择用户', + 'engine.inspector.recipients.placeholder.team': '选择团队', + 'engine.inspector.recipients.placeholder.email': 'name@example.com', + 'engine.inspector.recipients.placeholder.expression': 'CEL 表达式,例如 ownerId', + 'engine.inspector.recipients.placeholder.custom': '按原样保存,例如 role:admin', + 'engine.inspector.recipients.add': '添加收件人', + 'engine.inspector.recipients.remove': '移除收件人', + 'engine.inspector.recipients.empty': '还没有收件人。', + 'engine.inspector.recipients.notEmail': '这不是邮箱地址 —— 消息服务会把它当作用户 id。', + 'engine.inspector.recipients.kind.emailField': '邮箱字段', + 'engine.inspector.recipients.placeholder.emailField': '触发记录的邮箱字段', + 'engine.inspector.recipients.emailFieldHint': '向触发记录该字段中的地址发送邮件,仅走邮件渠道 —— 用于不是用户的人。它是本节点唯一的收件人。', + 'engine.inspector.recipients.emailFieldNotEmail': '这不是邮箱类型的字段 —— 这种收件人必须指向触发记录的邮箱类型字段。', + 'engine.inspector.recipients.emailFieldChannels': '这种收件人只走邮件渠道:本节点的渠道必须只有 email,否则节点会被拒收。', + 'engine.inspector.recipients.emailFieldChannelsFix': '只走邮件', + 'engine.inspector.recipients.fieldOnEmailNode': '本节点会发邮件:该字段中的地址必须属于某个用户,否则运行失败。要直接给该地址发邮件,请改用「邮箱字段」类型。', + // objectui#11788 — 新建/更新记录节点的字段值键,从目标对象中选取。 + 'engine.inspector.fieldMap.chooseObject': '请先在上方选择对象,才能列出它的字段。', + // objectui#11783 — the shared object picker (inspectors/ObjectPicker.tsx). + 'engine.inspector.objectPicker.groupPackage': '本软件包', + 'engine.inspector.objectPicker.groupOther': '其他对象', + 'engine.inspector.objectPicker.groupSystem': '系统对象', + 'engine.inspector.objectPicker.showSystem': '显示系统对象({count})', + 'engine.inspector.objectPicker.hideSystem': '隐藏系统对象', + 'engine.inspector.objectPicker.noMatch': '没有匹配的对象。按 Enter 保留输入的内容。', + // Add affordances + 'engine.inspector.add.widget': '添加组件', + 'engine.inspector.add.block': '添加区块', + 'engine.inspector.add.node': '添加节点', + 'engine.inspector.add.step': '添加步骤', + 'engine.inspector.add.action': '添加动作', + 'engine.inspector.add.nav': '添加导航项', + // 引用关系侧栏(ResourceEditPage)空态。 + 'engine.edit.refsScanning': '正在扫描引用…', + 'engine.edit.refsEmptyTitle': '未找到引用', + 'engine.edit.refsEmptyDesc': '元数据图中没有任何项指向它,可以安全删除。', + 'engine.edit.refsColFromType': '来源类型', + 'engine.edit.refsColFromName': '来源名称', + 'engine.edit.refsColPath': '路径', + // …以及它的失败态(objectui#5110):必须与上面的空态不可混淆 —— 文案只说明检查 + // 没有完成,对「删除是否安全」不做任何方向的断言,因为这个问题根本没有被回答。 + // 后续改写务必保留这一性质;「未找到引用」正是此状态要避免的那句话。 + 'engine.edit.refsErrorTitle': '引用检查失败', + 'engine.edit.refsErrorDesc': + '引用扫描没有完成,因此无法得知哪些内容依赖此项。这并不表示删除它是安全的 —— 请重新执行检查后再做决定。', + 'engine.edit.refsRetry': '重新检查', + // 破坏性变更(强制保存)对话框(ResourceEditPage)。 + 'engine.edit.destructiveTitle': '检测到破坏性变更', + 'engine.edit.destructiveDesc': '框架拒绝了本次保存,因为它会丢弃或收窄正在使用的数据。请检查这些问题并确认以强制覆盖。', + 'engine.edit.forcing': '强制保存中…', + // 历史页 / 面板(ResourceHistoryPage)。 + 'engine.edit.historySubtitle': '版本历史', + 'engine.edit.historyEvents': '事件', + 'engine.edit.historyEmptyTitle': '暂无历史', + 'engine.edit.historyEmptyDesc': '此项从未通过覆盖层编辑过。首次保存将创建初始历史记录。', + // Reorder buttons + 'engine.inspector.reorder.up': '上移', + 'engine.inspector.reorder.down': '下移', + // InspectorComboField defaults (objectui#10586). + 'engine.inspector.combo.search': '搜索或输入…', + 'engine.inspector.combo.noMatch': '无匹配项 —— 继续输入即可使用自定义值。', + 'engine.inspector.combo.loading': '加载中…', + 'engine.inspector.combo.useCustom': '使用 {value}', + 'engine.edit.overlay': '覆盖', + 'engine.edit.readOnlyBanner': '当前为只读模式。点击 {edit} 进行修改。', + 'engine.edit.readOnlyTypeBanner': + '出于安全考虑,此元数据类型为只读:它包含可执行代码或敏感配置,不允许在运行时通过覆盖层修改。请编辑包内源代码后重新部署。运维可将 {flag} 设置为包含 {type}(请谨慎使用);这只会开放该类型中不由任何包提供的条目,永远不会开放由包提供的条目。', + 'engine.edit.artifactLockedBanner': + '此 {type} 来自已安装的包,因此在运行时为只读。如需修改,请在其所属包的源中编辑并重新发布——或从零新建一个 {type}。', + 'engine.edit.readOnly': '只读(运行时覆盖未启用)。', + 'engine.edit.loading': '加载中', + 'engine.edit.bespokeDesigner': '可视化编辑器', + 'engine.edit.readOnlyHint': + '只读 — 该元数据类型不支持运行时覆盖。请修改包内源代码并重新部署。', + 'engine.edit.unsaved': '未保存', + 'engine.edit.diagnostics.title': '元数据不符合规范 — 共 {count} 个校验错误。', + 'engine.edit.diagnostics.warnTitle': '{count} 个警告 — 建议复查。', + 'engine.edit.diagnostics.more': '还有 {count} 个…', + 'engine.edit.loadFailed': '加载 {type}/{name} 失败:{message}', + 'engine.edit.unsavedHint': '当前存在未保存的修改。', + 'engine.edit.unsavedLeaveConfirm': '当前存在未保存的修改,确定离开此页面吗?', + 'engine.edit.saving': '保存中…', + 'engine.edit.savedAt': '已保存 {time}', + 'engine.edit.autoSaveOn': '自动保存已开启,点击关闭', + 'engine.edit.autoSaveOff': '自动保存已关闭,点击开启', + 'engine.edit.autoSavingShortly': '即将自动保存…', + 'engine.edit.destructive': '破坏性变更', + 'engine.edit.destructiveHint': '该修改会破坏现有引用关系。请检查问题清单后确认强制保存。', + 'engine.edit.forceSave': '强制保存', + 'engine.form.selectEllipsis': '请选择…', + 'engine.form.add': '添加', + 'engine.form.addItem': '添加项', + 'engine.form.addRow': '添加行', + 'engine.form.remove': '删除', + 'engine.form.removeRow': '删除行', + 'engine.form.dragToReorder': '拖动排序', + 'engine.form.arrayPlaceholder': '用逗号分隔多个值', + 'engine.form.colorHex': '十六进制值', + 'engine.form.loadingObjects': '正在加载对象…', + 'engine.form.noObjects': 'object_name(未检测到对象)', + 'engine.form.selectObject': '选择对象…', + 'engine.form.selectComponent': '选择组件…', + 'engine.form.noComponents': 'component_id(此页面暂无组件)', + 'engine.form.addObjects': '添加对象…', + 'engine.form.loadingFields': '正在加载字段…', + 'engine.form.selectObjectFirst': '(请先选择对象)', + 'engine.form.selectField': '选择字段…', + 'engine.form.addFields': '添加字段…', + 'engine.form.noObjectBound': '未绑定对象', + // The zh half of the shared picker failure state (objectui#5170). Same rule + // as the en table above: this copy must never be confusable with the empty + // copy around it, and must make no claim in either direction about whether + // options exist. + 'engine.form.loadingOptions': '正在加载选项…', + 'engine.form.optionsLoadFailedTitle': '选项加载失败', + 'engine.form.optionsLoadFailedDesc': + '这个列表没有加载成功,因此它并不表示不存在可选项。请重新加载编辑器后再试。', + 'engine.form.none': '— 无 —', + 'engine.form.notInObject': '(不在对象中)', + // The zh half of the unknown-value flags (objectui#9652). No space in + // `flaggedValue`: every flag opens with a full-width bracket. + 'engine.form.notFound': '(未找到)', + 'engine.form.deprecated': '(已弃用)', + 'engine.form.invalid': '(无效)', + 'engine.form.notPublished': '(未发布)', + 'engine.form.flaggedValue': '{value}{flag}', + 'engine.form.searchIcons': '搜索图标…', + 'engine.form.chooseIcon': '选择图标', + 'engine.form.iconsTruncated': '显示前 {shown} 个,共 {total} 个 —— 继续输入以缩小范围。', + 'engine.form.noMatchingIcons': '没有匹配的图标。', + 'engine.form.unknownIcon': '没有名为“{name}”的图标。请从列表中选择一个。', + 'engine.form.keep': '保留', + 'engine.form.addField': '添加字段…', + 'engine.form.addFieldPlain': '添加字段', + 'engine.form.searchFields': '搜索字段…', + 'engine.form.allFieldsAdded': '已添加全部字段', + // objectui#12126 — zh half of the `ref-multi:permission` add control. + 'engine.form.addPermissionSetPlain': '添加权限集', + 'engine.form.addPermissionSet': '添加权限集…', + 'engine.form.allPermissionSetsAdded': '已添加全部权限集', + 'engine.form.noObjectFields': '没有对象字段({error})。', + // objectui#10862, slice 4. + 'engine.form.objectNotFound': '未找到对象', + 'engine.form.datasetNotFound': '未找到数据集', + 'engine.form.noMatchingFields': '没有匹配字段。', + 'engine.form.noFieldsOnObject': '此对象暂无字段。', + 'engine.form.added': '已添加', + 'engine.form.tagsPlaceholder': '输入后按 Enter…', + 'engine.form.noRows': '暂无行。点击 + 添加。', + 'engine.form.masterDetailSchemaError': 'master-detail 组件要求 JSON Schema 上存在 items.properties(或 anyOf 分支包含该结构)。', + 'engine.form.missingField': '表单布局中声明了字段 {field},但 schema 中不存在,已跳过。', + 'engine.form.fallbackJson': '组件 {widget} 尚未注册自定义渲染器,暂时使用 JSON 编辑。', + 'engine.form.keyRequired': '键名必填', + 'engine.form.keyExists': '键名 "{key}" 已存在', + 'engine.form.keyPattern': '键名必须匹配 {pattern}', + 'engine.form.invalidJson': 'JSON 无效', + 'engine.form.rootJsonObject': '根节点必须是 JSON 对象', + // objectui#8218 — zh half of the same sweep. Wording targets the AI-build + // maker who now reads this panel, not a metadata administrator. + 'engine.form.machineName': '机器名', + 'engine.form.sectionN': '第 {n} 节', + 'engine.form.notConfigured': '尚未配置', + 'engine.form.configure': '配置', + 'engine.form.removeNamed': '删除 {name}', + 'engine.form.moveUp': '上移', + 'engine.form.moveDown': '下移', + 'engine.form.noneShort': '无', + 'engine.form.filterModeNone': '无', + 'engine.form.filterModeTabs': '标签页', + 'engine.form.filterModeDropdown': '下拉框', + 'engine.form.filterElement': '筛选控件', + 'engine.form.removeField': '删除字段', + 'engine.form.addFilterField': '+ 添加筛选字段…', + 'engine.form.bindObjectForFilterFields': '先绑定来源对象,才能挑选筛选字段。', + 'engine.form.tabLabel': '标签名称', + 'engine.form.moveTabUp': '标签上移', + 'engine.form.moveTabDown': '标签下移', + 'engine.form.removeTab': '删除标签', + 'engine.form.bindObjectForTabRules': '先绑定来源对象,才能配置标签筛选规则。', + 'engine.form.bindObjectForConditions': '先绑定来源对象,才能添加筛选条件。', + 'engine.form.addActionButtonPlain': '添加操作按钮', + 'engine.form.addActionButton': '+ 添加操作按钮…', + 'engine.form.allActionsAdded': '操作已全部添加', + 'engine.form.bindObjectForActions': '先绑定来源对象,才能挑选操作', + 'engine.form.color': '颜色', + 'engine.form.colorHexPlaceholder': '#RRGGBB', + 'engine.form.secretSetTypeToReplace': '•••••••• 已设置 —— 输入新值可替换', + 'engine.form.secretSetLeaveBlank': '•••••••• 已设置 —— 留空则保持不变', + 'engine.form.enterAValue': '请输入值', + 'engine.form.secretValue': '密钥值', + 'engine.form.hideValue': '隐藏值', + 'engine.form.revealValue': '显示值', + 'engine.form.clear': '清除', + 'engine.form.anOption': '一个选项', + 'engine.form.selectDepToConfigure': '请先选择{dep},然后再配置。', + 'engine.form.noConfigNeeded': '“{value}”无需额外配置。', + 'engine.form.readOnly': '只读', + 'engine.form.loadingEditor': '正在加载编辑器…', + 'engine.packages.title': '软件包', + 'engine.packages.description': '创建、发布和管理本系统中安装的软件包。', + 'engine.packages.refresh': '刷新', + 'engine.packages.import': '导入', + 'engine.packages.importing': '导入中…', + 'engine.packages.new': '新建软件包', + 'engine.packages.moreActions': '更多操作', + 'engine.packages.search': '搜索软件包…', + 'engine.packages.showPlatform': '显示平台软件包({count})', + 'engine.packages.empty': '暂无软件包', + 'engine.packages.emptyCreate': '创建第一个软件包以开始编写元数据。', + 'engine.packages.emptyFiltered': '没有匹配筛选条件的软件包。', + 'engine.packages.installedApps.title': '已安装的应用', + 'engine.packages.installedApps.empty': '尚未从应用市场安装任何应用', + 'engine.packages.installedApps.emptyHint': '从应用市场安装的应用会列在这里。', + 'engine.packages.loadFailed': '加载软件包失败', + 'engine.packages.col.name': '名称', + 'engine.packages.col.version': '版本', + 'engine.packages.col.scope': '范围', + 'engine.packages.col.status': '状态', + 'engine.packages.scope.project': '只读 · 代码包', + 'engine.packages.scope.system': '系统', + 'engine.packages.scope.cloud': '云端', + 'engine.packages.scope.writable': '可写', + 'engine.packages.scope.readOnly': '只读', + 'engine.packages.scope.unknown': '软件包「{id}」已被拒绝:服务器未说明它是否可写。', + 'engine.packages.col.source': '来源', + 'engine.packages.col.actions': '操作', + 'engine.packages.source.code': '代码', + 'engine.packages.source.marketplace': '应用市场', + 'engine.packages.source.authored': '自建', + 'engine.packages.source.showAll': '显示全部', + 'engine.packages.source.unknown': '软件包「{id}」已被拒绝:服务器未给出它的来源,或给出的来源不是 code、marketplace 或 authored 之一。', + 'engine.packages.source.filterUnknown': '未知的来源筛选值「{value}」。请使用 code、marketplace 或 authored。', + 'engine.packages.source.filterRepeated': '来源筛选给出了不止一次({value})。请只给出 code、marketplace 或 authored 之一。', + 'engine.packages.status.enabled': '已启用', + 'engine.packages.status.disabled': '已禁用', + 'engine.packages.import.invalidJson': '所选文件不是有效 JSON。', + 'engine.packages.import.invalidPackage': '无效的软件包文件:缺少 "id" 或 "name"。', + 'engine.packages.import.success': '已导入 "{id}"。刷新应用切换器后即可使用。', + 'engine.packages.import.failed': '导入失败', + 'engine.packages.create.title': '新建软件包', + 'engine.packages.create.description': '创建可写的软件包,用来编写和发布你自己的元数据。ID 建议使用反向域名格式(例如 {example})。', + 'engine.packages.create.id': '软件包 ID', + 'engine.packages.create.namespace': '对象命名空间', + 'engine.packages.create.name': '显示名称', + 'engine.packages.idRule.formatHint': '请使用反向域名格式的 ID,例如 {example}:由点分隔的两段或更多段,每段只含小写字母、数字和段内连字符,不能使用下划线。', + 'engine.packages.idRule.strippedNotice': '已移除不支持的字符——仅允许小写字母、数字、点和连字符', + 'engine.packages.idRule.derived': '编辑前会根据显示名称自动填写。', + 'engine.packages.idRule.notDerived': '显示名称里没有可用来生成 ID 的英文字母,请手动填写,例如 {example}。', + 'engine.packages.nsRule.derived': '编辑前会根据软件包 ID 自动填写。', + 'engine.packages.nsRule.required': '必填:本软件包内每个对象名称都以它开头。', + 'engine.packages.versionRule.default': '初始版本号,编辑前保持不变。', + 'engine.packages.create.version': '版本', + 'engine.packages.create.versionInvalid': '请使用语义化版本号,例如 0.1.0', + 'engine.packages.create.creating': '创建中…', + 'engine.packages.create.submit': '创建软件包', + 'engine.packages.create.failed': '创建软件包失败', + 'engine.packages.create.exists': '已存在相同 ID 的软件包。', + // objectstack#8270 — 这条正是在 zh 控制台里显示为英文原文的服务端拒绝信息。 + 'engine.packages.noCapability': '你没有在本系统管理软件包的权限 — 该操作需要「管理元数据」权限。', + 'engine.packages.form.basics': '基本信息', + 'engine.packages.form.advanced': '高级', + 'engine.packages.form.namespace': '命名空间', + 'engine.packages.form.defaultDatasource': '默认数据源', + 'engine.packages.form.scope': '范围', + 'engine.packages.form.dependencies': '依赖', + // objectui#13278 — the Dependencies row's key/value editor. + 'engine.packages.dependencies.package': '软件包', + 'engine.packages.dependencies.range': '版本范围', + 'engine.packages.dependencies.add': '添加依赖', + 'engine.packages.dependencies.remove': '移除依赖', + 'engine.packages.dependencies.empty': '暂无依赖。', + 'engine.packages.dependencies.nameRequired': '请输入软件包名称。', + 'engine.packages.dependencies.duplicate': '该软件包已在列表中。', + // `add10d8f9` — help text for the package manifest form. + // + // The LABELS above have always been translated while the help line under + // each one stayed English, so `新建软件包` rendered 显示名称 over + // "Human-readable package name" — a translated label and an untranslated + // sentence on consecutive lines of the same field. + // + // These are zh-ONLY on purpose, and the EN table deliberately has no + // counterpart. The English help text is `@objectstack/spec`'s own + // `ManifestSchema` `.describe()` (framework repo), which the form already + // reads through the spec-derived JSONSchema; `getPackageForm` looks these up + // with `tOptional`, so en-US finds nothing here and keeps falling through to + // the spec's sentence. Copying the English into this table instead would + // create a SECOND English source of truth that silently diverges the next + // time the spec is edited — the exact producer-side hazard the ObjectUI + // contract-first rule exists to stop. Only the zh rendering belongs here. + 'engine.packages.form.help.name': '软件包的可读名称。', + 'engine.packages.form.help.id': '软件包的唯一标识,采用反向域名格式。', + 'engine.packages.form.help.namespace': '简短的命名空间标识;同时是本软件包内每个对象名称的强制前缀(例如 “todo” → 对象名 “todo_task”、“todo_project”)。', + 'engine.packages.form.help.version': '软件包版本(语义化版本号)。', + 'engine.packages.form.help.type': '软件包类型。', + 'engine.packages.form.help.description': '软件包描述。', + 'engine.packages.form.help.defaultDatasource': '本软件包内所有对象的默认数据源。', + 'engine.packages.form.help.scope': '部署范围:cloud(云端)、system(系统)、project(项目)。', + 'engine.packages.form.help.dependencies': '软件包依赖。', + 'engine.packages.view.title': '软件包信息', + 'engine.packages.detail.viewInfo': '查看信息', + 'engine.packages.detail.type': '类型', + 'engine.packages.detail.description': '描述', + 'engine.packages.detail.browseMetadata': '浏览此软件包的元数据', + 'engine.packages.detail.pendingChanges': '待发布更改', + 'engine.packages.detail.pendingHint': '草稿尚未发布。可以发布整个应用,也可以逐项查看。', + 'engine.packages.detail.publishApp': '发布应用({count})', + 'engine.packages.detail.discardChanges': '丢弃更改({count})', + 'engine.packages.detail.kernelReadOnly': '这是平台内核软件包,已禁用编写操作。', + 'engine.packages.detail.actions': '操作', + 'engine.packages.detail.edit': '编辑', + 'engine.packages.edit.title': '编辑软件包', + 'engine.packages.edit.save': '保存更改', + 'engine.packages.edit.saving': '保存中…', + 'engine.packages.edit.saved': '软件包已更新', + 'engine.packages.edit.failed': '更新软件包失败', + 'engine.packages.detail.publish': '发布', + 'engine.packages.detail.publishing': '发布中…', + 'engine.packages.detail.revert': '还原', + 'engine.packages.detail.enable': '启用', + 'engine.packages.detail.disable': '禁用', + 'engine.packages.detail.export': '导出', + 'engine.packages.detail.exporting': '导出中…', + 'engine.packages.detail.deleteApp': '删除应用', + 'engine.packages.detail.deleting': '删除中…', + 'engine.packages.detail.discarding': '丢弃中…', + 'engine.packages.detail.actionFailed': '操作失败', + 'engine.packages.detail.publishBlocked': '发布被 {count} 个校验错误阻止。', + 'engine.packages.detail.nothingToPublish': '没有可发布的内容。', + 'engine.packages.detail.published': '软件包已发布。', + 'engine.packages.detail.publishDraftsPartial': '已发布 {published} 项;{failed} 项失败。', + 'engine.packages.detail.publishDraftsRolledBack': '发布未生效 — 批次已整体回滚(全有或全无):{cause}', + 'engine.packages.detail.publishDraftsOk': '应用已发布,所有草稿均已生效。', + 'engine.packages.detail.reverted': '已还原到上次发布状态。', + 'engine.packages.detail.discardDraftsPartial': '已丢弃 {discarded} 项;{failed} 项失败。', + 'engine.packages.detail.discardDraftsOk': '所有待发布更改已丢弃,已回到发布版本。', + 'engine.packages.detail.discardTitle': '丢弃全部待发布更改({count})?', + 'engine.packages.detail.discardDescription': '此应用的所有草稿都将被丢弃,应用回到上次发布的版本。已发布的元数据和数据不受影响。被丢弃的草稿无法恢复。', + 'engine.packages.detail.deleteTitle': '删除 "{name}"?', + 'engine.packages.detail.deleteDescription': '这会移除此软件包中的所有对象、视图、仪表板和应用。请选择如何处理这些对象中的记录。此操作不可撤销。', + 'engine.packages.detail.deleteModeStructure': '仅删除结构,保留记录', + 'engine.packages.detail.deleteModeStructureHint': '移除软件包的元数据。其对象创建的数据库表及其中的所有记录均保留。', + 'engine.packages.detail.deleteModeAll': '删除结构和数据', + 'engine.packages.detail.deleteModeAllHint': '同时删除其对象创建的数据库表及其中的所有记录。', + 'engine.packages.detail.deleteTypeName': '输入 "{name}" 以确认', + 'engine.packages.detail.duplicateGo': '创建副本', + 'engine.packages.detail.deleteFailed': '删除失败', + 'engine.packages.detail.disabled': '软件包已禁用。', + 'engine.packages.detail.enabled': '软件包已启用。', + 'engine.packages.detail.exported': '软件包已导出。', + 'engine.packages.detail.duplicate': '复制', + 'engine.packages.detail.duplicating': '复制中…', + 'engine.packages.detail.duplicated': '软件包已复制为新基座。', + 'engine.packages.detail.adoptOrphans': '收编散落项', + 'engine.packages.detail.adopting': '收编中…', + 'engine.packages.detail.adoptTitle': '把散落项收编进 "{name}" 吗?', + 'engine.packages.detail.adoptDescription': '本系统中所有无软件包(散落)的元数据项都会移入此基座。已属于某个软件包的项保持不动。', + // zh has one plural form, so the `…One` / `…Other` pair carries the same row. + 'engine.packages.detail.adoptedOne': '已收编 {count} 个散落项进此基座。', + 'engine.packages.detail.adoptedOther': '已收编 {count} 个散落项进此基座。', }; export default STUDIO_ZH; From 61332921445a2d87af186c9b3633d4aac7601723 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 16:59:32 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test(ui):=20S3=20designer=20rows=20read=20f?= =?UTF-8?q?rom=20the=20studio=20namespace=20=E2=80=94=20pin=20widened,=20z?= =?UTF-8?q?h=20preloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twenty suites that assert the moved zh rows synchronously make the zh catalogue resident first (one of them in the shared-graph unit project, where it passed only when another file had loaded zh). The namespace pin now covers the S3 families and the zh-only package help rows. Patch changeset for @object-ui/i18n. Claude-Session: https://claude.ai/code/session_01JQwxbX9LnJhjTQPJEmoBYi Co-authored-by: Claude --- .changeset/25922-studio-i18n-editor-chrome.md | 10 +++ .../AuditPanel.loadFailure.test.tsx | 5 ++ ...conPickerWidget.unknownName-12531.test.tsx | 5 ++ .../PackageFormDialog.helpTextI18n.test.tsx | 5 ++ .../PackageFormDialog.idDerive-11792.test.tsx | 5 ++ .../metadata-admin/PackageFormDialog.test.tsx | 5 ++ .../PackagesPage.installedApps-13356.test.tsx | 5 ++ ...esourceEditPage.pickerLoadFailure.test.tsx | 5 ++ ...eEditPage.referencesColumns-25916.test.tsx | 5 ++ ...urceEditPage.referencesErrorState.test.tsx | 5 ++ .../SchemaForm.deDeveloperize-8218.test.tsx | 5 ++ .../artifact-locked-banner.i18n.test.ts | 6 ++ ...age.selectPlaceholderLocale-11252.test.tsx | 5 ++ .../DashboardDefaultInspector.test.tsx | 5 ++ ...lowReferenceField.flag.i18n-10448.test.tsx | 5 ++ ...kDefaultInspector.flag.i18n-10448.test.tsx | 5 ++ .../ReportDefaultInspector.i18nTitle.test.tsx | 5 ++ .../ReportDefaultInspector.test.tsx | 5 ++ ...ectorStrings.studioNamespace-25922.test.ts | 63 ++++++++++++------- .../unknownValueFlag.i18n-9652.test.tsx | 5 ++ .../previewStrings.i18n-10835.test.tsx | 5 ++ .../selector-placeholder.i18n.test.tsx | 5 ++ 22 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 .changeset/25922-studio-i18n-editor-chrome.md diff --git a/.changeset/25922-studio-i18n-editor-chrome.md b/.changeset/25922-studio-i18n-editor-chrome.md new file mode 100644 index 00000000000..1d969c1be9f --- /dev/null +++ b/.changeset/25922-studio-i18n-editor-chrome.md @@ -0,0 +1,10 @@ +--- +'@object-ui/i18n': patch +--- + +feat(i18n): the rest of the Studio inspectors and the package, form and editor strings move into the `studio` namespace + +Clause-②: no +Metadata-protocol: none + +- **Third slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The remaining `engine.inspector.*` keys and every `engine.packages.*`, `engine.form.*` and `engine.edit.*` key (551 in `en`, 560 in `zh`) now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged. The nine `engine.packages.form.help.*` rows stay `zh`-only: their English is the producer's own help text, read through `tOptional`. 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 earlier slices. No export changes. diff --git a/ui/packages/app-shell/src/views/metadata-admin/AuditPanel.loadFailure.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/AuditPanel.loadFailure.test.tsx index dda77cf1c65..c8413b5200f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/AuditPanel.loadFailure.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/AuditPanel.loadFailure.test.tsx @@ -50,6 +50,11 @@ import userEvent from '@testing-library/user-event'; import type { MetadataAuditEntry, MetadataClient } from '@object-ui/data-objectstack'; import { AuditPanel } from './AuditPanel'; import { t } from './i18n'; +// The designer rows this suite reads in zh 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'); /** The positive claim this card exists to keep off a failed read. */ const EMPTY_TITLE = t('engine.edit.auditEmptyTitle', 'en-US'); diff --git a/ui/packages/app-shell/src/views/metadata-admin/IconPickerWidget.unknownName-12531.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/IconPickerWidget.unknownName-12531.test.tsx index 791fa006d69..530466ba4d7 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/IconPickerWidget.unknownName-12531.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/IconPickerWidget.unknownName-12531.test.tsx @@ -43,6 +43,11 @@ import { WIDGETS } from './widgets'; import { t, tFormat } from './i18n'; import { getIcon } from '../../utils/getIcon'; import { ActionDefaultInspector } from './inspectors/ActionDefaultInspector'; +// The designer rows this suite reads in zh 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/PackageFormDialog.helpTextI18n.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.helpTextI18n.test.tsx index cb3aad71e73..01d9855693d 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.helpTextI18n.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.helpTextI18n.test.tsx @@ -18,6 +18,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import { ManifestSchema } from '@objectstack/spec/kernel'; import { PackageFormDialog } from './PackageFormDialog'; +// The designer rows this suite reads in zh 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'); let localeFixture: 'en-US' | 'zh-CN' = 'zh-CN'; vi.mock('./i18n', async (importOriginal) => ({ diff --git a/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.idDerive-11792.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.idDerive-11792.test.tsx index ef84792bc85..a84dedf17b3 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.idDerive-11792.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.idDerive-11792.test.tsx @@ -22,6 +22,11 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/re import { ManifestSchema } from '@objectstack/spec/kernel'; import { PackageFormDialog } from './PackageFormDialog'; import { t } from './i18n'; +// The designer rows this suite reads in zh 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'); let localeFixture: 'en-US' | 'zh-CN' = 'en-US'; vi.mock('./i18n', async (importOriginal) => ({ diff --git a/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.test.tsx index 99a10d85682..e6eaddc568f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.test.tsx @@ -5,6 +5,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { PackageFormDialog } from './PackageFormDialog'; import { t } from './i18n'; +// The designer rows this suite reads in zh 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'); // Which console language the dialog renders in. Only `useMetadataLocale` is // replaced — the rest of `./i18n` (the `t` resolver and both string tables) is diff --git a/ui/packages/app-shell/src/views/metadata-admin/PackagesPage.installedApps-13356.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/PackagesPage.installedApps-13356.test.tsx index e0073b2ef08..f4368e6fe7f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/PackagesPage.installedApps-13356.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/PackagesPage.installedApps-13356.test.tsx @@ -32,6 +32,11 @@ import { resolveHref } from '@object-ui/layout'; import { PackagesPage, type InstalledPackageRow } from './PackagesPage'; import { t } from './i18n'; import { initRuntimeConfig, resetRuntimeConfigForTesting } from '../../runtime-config'; +// The designer rows this suite reads in zh 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 CODE: InstalledPackageRow = { manifest: { id: 'com.acme.code', name: 'Code App', version: '1.0.0', scope: 'project' }, diff --git a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.pickerLoadFailure.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.pickerLoadFailure.test.tsx index d2f10de959a..074edb93304 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.pickerLoadFailure.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.pickerLoadFailure.test.tsx @@ -141,6 +141,11 @@ vi.mock('./useMetadata', async (importOriginal) => { }); import { MetadataResourceEditPage, viewRefCatalog } from './ResourceEditPage'; +// The designer rows this suite reads in zh 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'); function mountEditor() { render( diff --git a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesColumns-25916.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesColumns-25916.test.tsx index e52275778a2..4cbb5101d6f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesColumns-25916.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesColumns-25916.test.tsx @@ -72,6 +72,11 @@ vi.mock('./useMetadata', async (importOriginal) => { }); import { MetadataResourceEditPage } from './ResourceEditPage'; +// The designer rows this suite reads in zh 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 START_PATH = '/metadata/object/showcase_account'; diff --git a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesErrorState.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesErrorState.test.tsx index 850bcd94a17..ff31a1d72fc 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesErrorState.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.referencesErrorState.test.tsx @@ -117,6 +117,11 @@ vi.mock('./useMetadata', async (importOriginal) => { }); import { MetadataResourceEditPage } from './ResourceEditPage'; +// The designer rows this suite reads in zh 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 START_PATH = '/metadata/object/showcase_account'; diff --git a/ui/packages/app-shell/src/views/metadata-admin/SchemaForm.deDeveloperize-8218.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/SchemaForm.deDeveloperize-8218.test.tsx index 2b6a5f43592..d374b84eb71 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/SchemaForm.deDeveloperize-8218.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/SchemaForm.deDeveloperize-8218.test.tsx @@ -38,6 +38,11 @@ import { I18nProvider } from '@object-ui/i18n'; import { SchemaForm } from './SchemaForm'; import { t } from './i18n'; import { localizeMetadataForm } from './metadata-form-i18n'; +// The designer rows this suite reads in zh 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/artifact-locked-banner.i18n.test.ts b/ui/packages/app-shell/src/views/metadata-admin/artifact-locked-banner.i18n.test.ts index 6fbf1e38077..08af7897fb3 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/artifact-locked-banner.i18n.test.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/artifact-locked-banner.i18n.test.ts @@ -6,6 +6,12 @@ */ import { describe, it, expect } from 'vitest'; import { t } from './i18n'; +// `engine.edit.*` lives in `@object-ui/i18n`'s `studio` namespace, whose `zh` +// catalogue is fetched on demand (objectstack#25922). Resident before any case: +// this file runs in the `unit` project, whose shared module graph would +// otherwise let it pass only when another file had loaded `zh` first. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); describe('artifactLockedBanner wording — F6', () => { it('[en] is accurate (not "shipped by a code package") and states the edit path', () => { diff --git a/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.selectPlaceholderLocale-11252.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.selectPlaceholderLocale-11252.test.tsx index dc53669490f..3623d87695e 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.selectPlaceholderLocale-11252.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.selectPlaceholderLocale-11252.test.tsx @@ -38,6 +38,11 @@ vi.mock('../useMetadata.js', () => ({ import { t } from '../i18n'; import { DatasourceResourcePage } from './DatasourceResourcePage'; +// The designer rows this suite reads in zh 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/DashboardDefaultInspector.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.test.tsx index 0ab2e12d516..e66e98feff6 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.test.tsx @@ -4,6 +4,11 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup } from '@testing-library/react'; import { DashboardDefaultInspector } from './DashboardDefaultInspector'; import { t } from '../i18n'; +// The designer rows this suite reads in zh 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/FlowReferenceField.flag.i18n-10448.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.flag.i18n-10448.test.tsx index 3f228a5a330..b99862af117 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.flag.i18n-10448.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.flag.i18n-10448.test.tsx @@ -54,6 +54,11 @@ vi.mock('../previews/useObjectFields', () => ({ import { t } from '../i18n'; import { FlowReferenceField } from './FlowReferenceField'; +// The designer rows this suite reads in zh 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/HookDefaultInspector.flag.i18n-10448.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/HookDefaultInspector.flag.i18n-10448.test.tsx index 91299d9a10f..1cf37dbb157 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/HookDefaultInspector.flag.i18n-10448.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/HookDefaultInspector.flag.i18n-10448.test.tsx @@ -39,6 +39,11 @@ vi.mock('../useMetadata', () => ({ import { t } from '../i18n'; import { HookDefaultInspector } from './HookDefaultInspector'; +// The designer rows this suite reads in zh 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/ReportDefaultInspector.i18nTitle.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.i18nTitle.test.tsx index eaf9ce6e65a..de9ffe81b95 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.i18nTitle.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.i18nTitle.test.tsx @@ -27,6 +27,11 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup } from '@testing-library/react'; import { ReportDefaultInspector } from './ReportDefaultInspector'; import type { DatasetCatalogEntry } from '../previews/useDatasetCatalog'; +// The designer rows this suite reads in zh 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/ReportDefaultInspector.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.test.tsx index 76b6ab5c3de..89ec6f77766 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.test.tsx @@ -4,6 +4,11 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup } from '@testing-library/react'; import { ReportDefaultInspector } from './ReportDefaultInspector'; import type { DatasetCatalogEntry } from '../previews/useDatasetCatalog'; +// The designer rows this suite reads in zh 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 index 8332a89e2f2..c9aaab9e761 100644 --- 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 @@ -1,11 +1,11 @@ // 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. + * objectstack#25922 slices S2 and S3 — the designer rows that moved into + * `@object-ui/i18n`'s `studio` namespace (every `engine.inspector.*` row, and + * `engine.packages.*`, `engine.form.*`, `engine.edit.*`), and app-shell's `t()` + * / `tOptional()` still answer every one of them the way they did when the rows + * sat in its own table. * * What would break it: * - a row copied back into the app-shell table with a different value — the @@ -21,34 +21,47 @@ */ import { describe, expect, it } from 'vitest'; import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; -import { t } from '../i18n'; +import { t, tOptional } 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, +/** Key prefix → the fewest `en` keys it may carry (measured at the move, rounded down). */ +const FAMILY_FLOOR: Readonly> = Object.freeze({ + // S2 + 'engine.inspector.pageBlock.': 160, + 'engine.inspector.action.': 120, + 'engine.inspector.dataset.': 80, + 'engine.inspector.flowNode.': 40, + 'engine.inspector.appNav.': 40, + 'engine.inspector.widget.': 40, + // S3 — the rest of the inspectors, and the package, form and editor chrome + 'engine.inspector.': 700, + 'engine.packages.': 120, + 'engine.form.': 90, + 'engine.edit.': 90, }); -const familyKeys = (family: string): string[] => - Object.keys(EN).filter((key) => key.startsWith(`engine.inspector.${family}.`)); +/** + * The one zh-only family among the moved rows: the package form's help text, + * whose English is the producer's own `.describe()` sentence and is read + * through `tOptional` (`ZH_ONLY_FAMILIES` in + * `ui/scripts/check-i18n-designer-table-parity.mjs` carries the reason). + */ +const ZH_ONLY_PREFIX = 'engine.packages.form.help.'; + +const familyKeys = (prefix: string): string[] => Object.keys(EN).filter((key) => key.startsWith(prefix)); -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); +describe('moved designer rows answer from the studio namespace (objectstack#25922)', () => { + it.each(Object.entries(FAMILY_FLOOR))('%s: the namespace carries the family, in en and zh', (prefix, floor) => { + const keys = familyKeys(prefix); 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) => { + it.each(Object.keys(FAMILY_FLOOR))('%s: t() answers every key with the namespace value', (prefix) => { + const wrong = familyKeys(prefix).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')}`); @@ -59,4 +72,12 @@ describe('S2 inspector rows answer from the studio namespace (objectstack#25922) }); expect(wrong).toEqual([]); }); + + it('the zh-only package help rows stay optional lookups: zh answers, en finds nothing', () => { + const keys = Object.keys(ZH).filter((key) => key.startsWith(ZH_ONLY_PREFIX)); + expect(keys.length).toBeGreaterThanOrEqual(9); + expect(keys.filter((key) => key in EN)).toEqual([]); + expect(keys.filter((key) => tOptional(key, 'zh-CN') !== ZH[key])).toEqual([]); + expect(keys.filter((key) => tOptional(key, 'en-US') !== undefined)).toEqual([]); + }); }); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/unknownValueFlag.i18n-9652.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/unknownValueFlag.i18n-9652.test.tsx index 2565293f6b3..1eb1aca99cb 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/unknownValueFlag.i18n-9652.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/unknownValueFlag.i18n-9652.test.tsx @@ -68,6 +68,11 @@ import { ViewVariantInspector } from './ViewVariantInspector'; import { FlowNodeConfigField } from './FlowNodeConfigField'; import { FlowObjectListField } from './FlowObjectListField'; import type { FlowConfigColumn, FlowConfigField } from './flow-node-config'; +// The designer rows this suite reads in zh 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/previews/previewStrings.i18n-10835.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10835.test.tsx index d03dde14474..a5c3c126969 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10835.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10835.test.tsx @@ -74,6 +74,11 @@ import { t, tFormat } from '../i18n'; import { DashboardPreview } from './DashboardPreview'; import { FlowSimulatorPanel } from './FlowSimulatorPanel'; import type { SimEdge, SimNode } from './simulator/flow-sim-types'; +// The designer rows this suite reads in zh 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/selector-placeholder.i18n.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/selector-placeholder.i18n.test.tsx index 905305161d8..d53b8482987 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/selector-placeholder.i18n.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/selector-placeholder.i18n.test.tsx @@ -5,6 +5,11 @@ import { render, screen, cleanup } from '@testing-library/react'; import { WIDGETS } from './widgets'; import { loaded } from './loadState'; import { t } from './i18n'; +// The designer rows this suite reads in zh 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();