diff --git a/.changeset/25922-studio-i18n-studio-previews.md b/.changeset/25922-studio-i18n-studio-previews.md new file mode 100644 index 00000000000..d264fda43d3 --- /dev/null +++ b/.changeset/25922-studio-i18n-studio-previews.md @@ -0,0 +1,10 @@ +--- +'@object-ui/i18n': patch +--- + +feat(i18n): the rest of the Studio strings and the metadata preview panes move into the `studio` namespace + +Clause-②: no +Metadata-protocol: none + +- **Fifth slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). Every remaining `engine.studio.*` key except `engine.studio.inspector.*`, and the preview panes' `engine.*Preview.*` and `engine.previewShell.*` keys (the doc, book, flow, page-block and translation previews stay for a later slice), 596 keys in both `en` and `zh`, now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged. 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/i18n.ts b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts index cee71c42f3e..7c61a7c166f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -370,19 +370,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.widgetPicker.type.funnel': 'Funnel', 'engine.widgetPicker.type.table': 'Data table', 'engine.widgetPicker.type.pivot': 'Pivot table', - // objectui#10835 — the dashboard designer's preview canvas (`DashboardPreview`): - // the empty-canvas message, the error-boundary hint, the loading text and the - // selected-widget strip. Display only: a widget's stored `title` is author - // data and shows as written. - 'engine.dashboardPreview.empty': 'Add at least one widget to see a preview.', - 'engine.dashboardPreview.malformed': "A widget references an object or field that doesn't resolve.", - 'engine.dashboardPreview.loading': 'Loading dashboard renderer…', - 'engine.dashboardPreview.selected': 'Selected', - 'engine.dashboardPreview.clickToRename': 'Click to rename', - 'engine.dashboardPreview.untitled': 'untitled', - 'engine.dashboardPreview.saveTitle': 'Save title', - 'engine.dashboardPreview.renameWidget': 'Rename widget', - 'engine.dashboardPreview.clearSelection': 'Clear selection', // ConditionBuilder raw-expression mode (CEL editor, #1582) 'engine.condition.celLabel': 'CEL expression', 'engine.condition.advancedHint': 'Advanced expression — Builder only supports simple AND/OR conditions.', @@ -557,155 +544,6 @@ const ENGINE_STRINGS_EN: Record = { // the producer gives none, is `engine.flowSim.note.evaluationFailed`. 'engine.flowSim.note.celEvaluationFailed': 'CEL evaluation failed: {message}', 'engine.flowSim.note.celConditionFailed': 'condition failed to evaluate as CEL: {message}', - // objectui#10848 — the preview chrome still English under zh-CN after - // objectui#10835: `PreviewErrorBoundary`'s heading (every designer preview), - // and `ScreenPreview`'s own words (the flow node inspector and the Debug - // run's paused screen). Display only: a screen's title, description and - // field labels are author data and show as written. - 'engine.previewShell.renderFailed': 'Preview failed to render', - 'engine.screenPreview.header': 'Preview', - 'engine.screenPreview.empty': 'Add a title, description, fields, or an object form to preview this screen.', - 'engine.screenPreview.submit': 'Submit', - 'engine.screenPreview.noDataSource': 'Connect to a backend to preview this object form.', - 'engine.screenPreview.hiddenOne': '{count} field hidden by its “visible when” condition.', - 'engine.screenPreview.hiddenOther': '{count} fields hidden by their “visible when” conditions.', - 'engine.screenPreview.defaultTemplate': 'Default template for “{field}”, shown as written — the run fills it in:', - // objectui#10862 (slice 1: the flow / automation previews) — the designer's - // own words in `ActionPreview`, `AgentPreview`, `SkillPreview`, - // `ToolPreview` and `JobPreview`, read in the `locale` each preview is - // handed. Each en row is the literal it replaced. Author data (labels, - // names, instructions, descriptions), identifiers (enum tokens, object and - // capability names, JSON-Schema types, compact durations such as `5m`) and - // producer text (a response body, a fetch error) show as written. - 'engine.actionPreview.empty': 'Set name and label to see the action preview.', - 'engine.actionPreview.pill.type': 'type: {type}', - 'engine.actionPreview.pill.object': 'object: {object}', - 'engine.actionPreview.pill.variant': 'variant: {variant}', - 'engine.actionPreview.pill.refreshAfter': 'refresh after', - 'engine.actionPreview.pill.aiOptedOut': 'AI: opted out', - 'engine.actionPreview.pill.aiExposed': 'AI: exposed', - 'engine.actionPreview.locations': 'Locations:', - 'engine.actionPreview.requires': 'Requires:', - 'engine.actionPreview.visibleWhen': 'Visible when', - 'engine.actionPreview.disabledWhen': 'Disabled when', - 'engine.actionPreview.onClick': 'On click', - 'engine.actionPreview.firstAsks': 'First asks:', - 'engine.actionPreview.onSuccess': 'On success:', - 'engine.actionPreview.handler.none': 'No handler bound yet.', - 'engine.actionPreview.handler.url': 'Navigate to {target}', - 'engine.actionPreview.handler.flow': 'Run flow {target}', - 'engine.actionPreview.handler.modal': 'Open modal {target}', - 'engine.actionPreview.handler.api': 'Call API endpoint {target}', - 'engine.actionPreview.handler.form': 'Open form view {target} (/console/forms/{path})', - 'engine.actionPreview.handler.scriptBody': 'Run inline script body (L1 expression or L2 sandboxed JS).', - 'engine.actionPreview.handler.scriptNamed': 'Run named script {target}', - 'engine.actionPreview.handler.invoke': 'Invoke {target}', - 'engine.actionPreview.section.placement': 'Where it appears', - 'engine.actionPreview.capabilityGateNote': - 'Hidden from anyone who does not hold {capabilities} — the frames below show the placement, not who can see it.', - 'engine.actionPreview.section.testRequest': 'Test request', - 'engine.actionPreview.section.inputDialog': 'Input Dialog', - 'engine.actionPreview.section.resultDialog': 'Result Dialog', - 'engine.actionPreview.section.scriptBody': 'Script Body ({language})', - 'engine.actionPreview.dialog.ok': 'OK', - 'engine.actionPreview.param.carryOver': 'Carried over from the selected row (read-only)', - 'engine.actionPreview.param.fromRow': '(from selected row)', - 'engine.actionPreview.param.selectPlaceholder': 'Select {label}', - 'engine.actionPreview.param.recordIdPlaceholder': 'Record id for {label}', - 'engine.actionPreview.param.noReference': - 'No {reference} object is configured, so the record picker is unavailable and the dialog asks for a record id.', - 'engine.actionPreview.param.toggle': 'Toggle', - 'engine.actionPreview.param.noChoices': 'No choices authored yet — the dialog opens an empty picker.', - 'engine.actionPreview.param.searchObject': 'Search {object}…', - 'engine.actionPreview.param.searchRecords': 'Search records…', - 'engine.actionPreview.result.title': 'Result', - 'engine.actionPreview.result.acknowledge': 'I have saved this', - 'engine.actionPreview.result.fullJson': 'Renders full JSON response.', - 'engine.actionPreview.placement.sampleRecord': 'Sample record', - 'engine.actionPreview.placement.recordDetail': 'Record detail', - 'engine.actionPreview.placement.recordBody': '…record body…', - 'engine.actionPreview.placement.records': 'Records', - 'engine.actionPreview.placement.rows': 'row 1 · row 2 · row 3', - 'engine.actionPreview.placement.row': 'Row {n}', - 'engine.actionPreview.placement.section': 'Section', - 'engine.actionPreview.placement.sectionBody': '…section content…', - 'engine.actionPreview.placement.more': 'More', - 'engine.actionPreview.api.paramsOne': '+ {count} user-supplied param collected at runtime', - 'engine.actionPreview.api.paramsOther': '+ {count} user-supplied params collected at runtime', - 'engine.actionPreview.api.liveWarning': 'Sends a real {method} request to the live backend — may modify data.', - 'engine.actionPreview.api.sending': 'Sending…', - 'engine.actionPreview.api.send': 'Send test request', - 'engine.actionPreview.api.networkError': 'Network error', - 'engine.agentPreview.tryInChat': 'Try in chat', - 'engine.agentPreview.tryInChatTitle': 'Open the saved version of this agent in a new chat', - 'engine.agentPreview.empty': 'Fill in label, role, and instructions in the Form tab to see the agent preview.', - 'engine.agentPreview.active': 'Active', - 'engine.agentPreview.disabled': 'Disabled', - 'engine.agentPreview.temperature': 'temp {value}', - 'engine.agentPreview.maxTokens': 'max {count}t', - 'engine.agentPreview.instructions': 'Instructions', - 'engine.agentPreview.noInstructions': 'No system prompt set yet.', - 'engine.agentPreview.capabilities': 'Capabilities', - 'engine.agentPreview.skills': 'Skills', - 'engine.agentPreview.noSkills': 'No skills attached — this agent can reach no tools.', - 'engine.agentPreview.toolsNote': 'Tools come from the attached skills; grounding is described in the instructions.', - 'engine.agentPreview.planning': 'Planning', - 'engine.agentPreview.memory': 'Memory', - 'engine.agentPreview.guardrails': 'Guardrails', - 'engine.agentPreview.permissions': 'Permissions', - 'engine.agentPreview.defaults': 'Defaults in use.', - 'engine.agentPreview.noValues': 'No values set.', - 'engine.skillPreview.empty': 'Give the skill a name and at least an instructions block to see the preview.', - 'engine.skillPreview.active': 'Active', - 'engine.skillPreview.disabled': 'Disabled', - 'engine.skillPreview.model': 'model: {model}', - 'engine.skillPreview.instructions': 'Instructions', - 'engine.skillPreview.noInstructions': 'No instructions yet — the skill will contribute nothing to the prompt.', - 'engine.skillPreview.tools': 'Tools ({count})', - 'engine.skillPreview.noTools': 'No tools whitelisted.', - 'engine.skillPreview.wildcardNote': - 'Wildcards expand to many tools at runtime — review the matching set in the agent before activating.', - 'engine.skillPreview.triggerConditions': 'Trigger Conditions ({count})', - 'engine.skillPreview.col.field': 'Field', - 'engine.skillPreview.col.operator': 'Operator', - 'engine.skillPreview.col.value': 'Value', - 'engine.skillPreview.allMustHold': 'All conditions must hold (AND) for the skill to activate.', - 'engine.skillPreview.missing': 'missing', - 'engine.skillPreview.missingTitle': 'Required by SkillTriggerConditionSchema — this draft will be rejected on save.', - 'engine.toolPreview.openApiConsole': 'Open in API Console', - 'engine.toolPreview.openApiConsoleTitle': 'Open API Console to test invocation', - 'engine.toolPreview.empty': 'Set name, description, and parameters to see the tool preview.', - 'engine.toolPreview.inputParameters': 'Input Parameters', - 'engine.toolPreview.noParameters': 'This tool takes no input parameters.', - 'engine.toolPreview.exampleCall': 'Example LLM Call', - 'engine.toolPreview.outputSchema': 'Output Schema', - 'engine.toolPreview.col.name': 'Name', - 'engine.toolPreview.col.type': 'Type', - 'engine.toolPreview.col.description': 'Description', - 'engine.toolPreview.required': 'req', - 'engine.toolPreview.enum': 'enum: {values}', - 'engine.toolPreview.default': 'default:', - 'engine.toolPreview.format': 'format: {format}', - 'engine.jobPreview.empty': 'Set a schedule (cron / every / at) and a handler to see the job preview.', - 'engine.jobPreview.active': 'Active', - 'engine.jobPreview.paused': 'Paused', - 'engine.jobPreview.retries': 'retries: {count}', - 'engine.jobPreview.retriesBackoff': 'retries: {count} ({backoff} backoff)', - 'engine.jobPreview.timeout': 'timeout: {duration}', - 'engine.jobPreview.concurrency': 'concurrency: {count}', - 'engine.jobPreview.schedule': 'Schedule', - 'engine.jobPreview.every': 'Every', - 'engine.jobPreview.at': 'At', - 'engine.jobPreview.unparseable': 'unparseable', - 'engine.jobPreview.noSchedule': 'No schedule set — runs only when triggered manually.', - 'engine.jobPreview.nextRunOne': 'Next {count} run', - 'engine.jobPreview.nextRunOther': 'Next {count} runs', - 'engine.jobPreview.noFireTime': 'Could not derive any future fire time. Check the schedule syntax.', - 'engine.jobPreview.noUpcoming': 'No upcoming runs in the next 366 days.', - 'engine.jobPreview.inDelta': 'in {delta}', - 'engine.jobPreview.inNow': 'in now', - 'engine.jobPreview.handler': 'Handler', - 'engine.jobPreview.noHandler': 'No handler bound — the job will be a no-op.', // objectui#10862 — the RLS policy editor's CEL try-it dialog: the result for // an empty predicate (`celAuthoring.testRunCelPredicate` returns it as a // `messageKey` the dialog reads through its `t`) and the sample-JSON @@ -714,29 +552,6 @@ const ENGINE_STRINGS_EN: Record = { // `engine.flowSim.note.evaluationFailed`, the same "Evaluation failed.". 'engine.celTest.predicateEmpty': 'The predicate is empty.', 'engine.celTest.notObject': 'expected a JSON object.', - // objectui#10862 (slice 2: the admin previews and the CEL lint advisories) — - // the designer's own words in `AppPreview`, `BookPreview`, - // `DatasourcePreview`, `EmailTemplatePreview`, `PermissionPreview`, - // `PositionPreview` and `TranslationPreview`, read in the `locale` each - // preview is handed. Each en row is the literal it replaced; a `{token}` a - // row names in code font (`{navigation}`, `{include}`, …) is filled with - // that code span, so the sentence keeps its order in both languages. Author - // data (labels, names, descriptions, doc names, variable names, config - // values), identifiers (nav kinds, enum values, access-scope tokens, the - // C / R / U / D column letters, `SSL`) and notation (`∅`, `#3`, `[2]`, the - // `3/8 (38%)` ratio) show as written. - 'engine.appPreview.openTitle': 'Open this app in a new tab', - 'engine.appPreview.open': 'Open', - 'engine.appPreview.homeFirst': 'Home: opens the first navigation item', - 'engine.appPreview.homeNone': 'Home: no navigation item yields a route yet', - 'engine.appPreview.empty': - "No top-level nav items. Add {navigation} entries in the Form tab to populate the app's navigation.", - 'engine.appPreview.unnamed': '(unnamed)', - 'engine.appPreview.noTypeTitle': - 'Every navigation item needs a `type` discriminator — this entry will be rejected on save.', - 'engine.appPreview.noType': 'no type', - // objectui#11027 — the heading over the app's navigation areas (`AppSchema.areas`). - 'engine.appPreview.areas': 'Areas', 'engine.bookPreview.nameTitle': 'Name your book', 'engine.bookPreview.nameDescription': 'Enter a name in the Form tab to start authoring the documentation spine.', 'engine.bookPreview.treeTitle': 'Open the live-resolved navigation tree (actual docs per group)', @@ -788,79 +603,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.docPreview.source': 'Markdown source', 'engine.docPreview.sourceLocale': 'Markdown source ({locale})', 'engine.docPreview.sourcePlaceholder': '# Title\n\nWrite Markdown here…', - 'engine.emailTemplatePreview.empty': 'Fill in the subject / body in the Form tab to see a preview.', - 'engine.emailTemplatePreview.emptyBody': '(empty body)', - 'engine.emailTemplatePreview.from': 'From', - 'engine.emailTemplatePreview.to': 'To', - 'engine.emailTemplatePreview.subject': 'Subject', - 'engine.emailTemplatePreview.frameTitle': 'Email preview', - 'engine.emailTemplatePreview.variables': 'Variables', - 'engine.emailTemplatePreview.noVariables': 'No {var} placeholders found.', - 'engine.emailTemplatePreview.samplePlaceholder': 'sample for {name}', - 'engine.permissionPreview.empty': 'Grant at least one object, system, or tab permission to see the matrix.', - 'engine.permissionPreview.kind.default': 'Default Permission Set', - 'engine.permissionPreview.kind.set': 'Permission Set', - 'engine.permissionPreview.pill.objects': '{count} objects', - 'engine.permissionPreview.pill.systemPerms': '{count} system perms', - 'engine.permissionPreview.pill.tabs': '{count} tabs', - 'engine.permissionPreview.pill.rls': '{count} RLS rules', - 'engine.permissionPreview.section.objects': 'Object Permissions', - 'engine.permissionPreview.section.fields': 'Field-Level Overrides', - 'engine.permissionPreview.section.system': 'System Permissions', - 'engine.permissionPreview.section.tabs': 'Tab Visibility', - 'engine.permissionPreview.col.object': 'Object', - 'engine.permissionPreview.cap.create': 'Create', - 'engine.permissionPreview.cap.read': 'Read', - 'engine.permissionPreview.cap.edit': 'Edit', - 'engine.permissionPreview.cap.delete': 'Delete', - 'engine.permissionPreview.cap.export': 'Export', - 'engine.permissionPreview.cap.transfer': 'Transfer', - 'engine.permissionPreview.cap.viewAll': 'View All', - 'engine.permissionPreview.cap.modifyAll': 'Modify All', - 'engine.permissionPreview.col.scopeTitle': 'Read / write access depth (ADR-0057)', - 'engine.permissionPreview.col.scope': 'Scope', - 'engine.permissionPreview.readScope': 'Read scope', - 'engine.permissionPreview.writeScope': 'Write scope', - 'engine.permissionPreview.scopeDefault': 'default', - 'engine.permissionPreview.fls.hidden': 'hidden', - 'engine.permissionPreview.fls.editable': 'editable', - 'engine.permissionPreview.sanityOne': '{count} sanity check failed', - 'engine.permissionPreview.sanityOther': '{count} sanity checks failed', - 'engine.permissionPreview.warn.editWithoutRead': 'Edit granted without Read (record updates will fail).', - 'engine.permissionPreview.warn.deleteWithoutRead': 'Delete granted without Read.', - 'engine.permissionPreview.warn.modifyAllWithoutViewAll': - 'Modify All without View All — modifications may target invisible records.', - 'engine.permissionPreview.warn.writeWiderThanRead': - 'Write scope ({write}) is wider than read scope ({read}) — edits may target invisible records.', - 'engine.permissionPreview.granted': 'granted', - 'engine.permissionPreview.notGranted': 'not granted', - 'engine.permissionPreview.legend.bypass': 'bypass (View/Modify All)', - 'engine.permissionPreview.legend.key': 'C R U D = CRUD · E T = Export/Transfer · V* M* = View/Modify All', - 'engine.positionPreview.empty': 'Give the position a name to see the preview.', - 'engine.positionPreview.noAccess': "Positions don't grant data access on their own.", - 'engine.positionPreview.bind': - 'Bind {name} to one or more {permissionSets} to control CRUD-VAMA, field access, and tab visibility. Record visibility comes from the business-unit tree and sharing rules, not from the position.', - 'engine.positionPreview.permissionSets': 'Permission Sets', - // objectui#13304 — a position that declares `permissionSets` (ADR-0131 D4). - 'engine.positionPreview.grantsVia': 'This position grants access through these {permissionSets}:', - 'engine.positionPreview.visibility': - 'Record visibility comes from the business-unit tree and sharing rules, not from the position.', - // objectui#10202 — the read-only picklist page (the kind is package-owned). - 'engine.picklistPreview.empty': 'This picklist has no name.', - 'engine.picklistPreview.readOnly': - 'Read-only: a picklist is owned by the package that declares it, and is not created, edited or deleted here.', - 'engine.picklistPreview.ownedBy': 'Owning package', - 'engine.picklistPreview.options': 'Options', - 'engine.picklistPreview.colLabel': 'Label', - 'engine.picklistPreview.colValue': 'Value', - 'engine.picklistPreview.noOptions': 'This picklist declares no options.', - 'engine.picklistPreview.extensions': 'Extensions', - 'engine.picklistPreview.extensionsHint': 'Options other packages add to this picklist.', - 'engine.picklistPreview.addedBy': 'Added by {package}', - 'engine.picklistPreview.noExtensions': 'No package extends this picklist.', - 'engine.picklistPreview.extensionsLoading': 'Loading extensions…', - 'engine.picklistPreview.extensionsFailed': - 'The installed packages could not be read, so the extensions of this picklist are unknown.', 'engine.translationPreview.empty': 'This bundle is empty — add at least one translated string to see the coverage report.', // One heading per group of the per-app `TranslationDataSchema`, in its order (objectui#11765). @@ -895,58 +637,6 @@ const ENGINE_STRINGS_EN: Record = { // ships `@objectstack/lint`'s own message, objectui#13061.) 'engine.celLint.notTheRow': '`{identifier}` is not the row on this surface: a row predicate binds the record as `{canonical}` and nothing else. The CEL scope vocabulary still accepts `{identifier}`, so nothing here blocks the save, but at runtime the expression faults with `Unknown variable: {identifier}` and the rule never fires. Re-root the reference on `{canonical}`.', - // objectui#10862 (slice 3) — the object / data previews (`ObjectPreview`, - // `DatasetPreview`, `ViewPreview`, `ReportPreview`, `PagePreview`, - // `PageBlockCanvas` and its add-block picker, `SourcePageEditor`, - // `ValidationPreview`, the view column manager) and the federated - // datasource panel, each en row the literal it replaces. Author data (names, - // labels, messages, tags, region and slot names, CEL and regex sources) and - // producer text (the error a renderer threw, an engine or HTTP message) stay - // as written. - 'engine.objectPreview.renderFailed': "The form designer couldn't be rendered. Check the Form tab.", - 'engine.datasetPreview.pickObject': 'Pick a base object', - 'engine.datasetPreview.pickObjectHint': "Set the dataset's `object` to preview it against live data.", - 'engine.datasetPreview.addMeasure': 'Add a measure', - 'engine.datasetPreview.addMeasureHint': - 'A dataset needs at least one measure (e.g. revenue = sum(amount)) to preview.', - 'engine.datasetPreview.run': 'Run preview', - 'engine.datasetPreview.measureOne': '{count} measure', - 'engine.datasetPreview.measureOther': '{count} measures', - 'engine.datasetPreview.dimensionOne': '{count} dimension', - 'engine.datasetPreview.dimensionOther': '{count} dimensions', - 'engine.datasetPreview.notInstalled': 'Analytics capability not installed', - 'engine.datasetPreview.notInstalledHint': - "This deployment has no analytics service, so dataset previews can't run. Install @objectstack/service-analytics and mount AnalyticsServicePlugin — the dataset definition itself is fine.", - 'engine.datasetPreview.noRows': 'No rows', - 'engine.datasetPreview.noRowsHint': 'The dataset returned no rows for the current scope.', - 'engine.datasetPreview.chartFailed': - "Couldn't render the chart for this result — the table below still shows the data.", - 'engine.datasetPreview.ratioAxis': 'Ratio measures ({measures}) use the right axis.', - 'engine.viewPreview.schemaFailed': - "The view's `type` may not be registered, or required fields are missing.", - 'engine.viewPreview.noObject': - 'This view has no object binding yet. Set the bound {object} in the right panel to fetch live data and field options.', - 'engine.viewPreview.formFailed': "The form view references an object or field that doesn't resolve.", - 'engine.viewPreview.listFailed': "The view references an object or field that doesn't resolve.", - 'engine.reportPreview.renderFailed': - "The Report references a dataset/measure that doesn't resolve, or its config is incomplete.", - 'engine.reportPreview.loading': 'Loading report renderer…', - 'engine.reportPreview.joinedEmpty': 'Add a block to preview this joined report', - 'engine.reportPreview.joinedEmptyHint': - 'A joined report stacks dataset-bound blocks. Add a block and bind its dataset + measures in the right panel to start designing.', - 'engine.reportPreview.empty': 'Bind a dataset to preview this report', - 'engine.reportPreview.emptyHint': - "Since the 9.0 single-form cutover a report renders its dataset's measures (values) grouped by dimensions (rows). Choose a Dataset in the right panel to start designing.", - 'engine.pagePreview.interfaceFailed': - "The interface page references a source object/view that isn't available.", - 'engine.pagePreview.schemaFailed': - "The Page schema is incomplete or references a component that hasn't been registered yet.", - 'engine.pagePreview.previewRecord': 'Preview record', - 'engine.pagePreview.sampleOne': '{count} sample', - 'engine.pagePreview.sampleOther': '{count} samples', - 'engine.pagePreview.recordFallback': '(record)', - 'engine.pagePreview.addComponents': 'Add components to the page to see a preview.', - 'engine.pagePreview.blockN': 'block {n}', 'engine.pageBlockCanvas.renderFailed': '"{type}" can\'t render with its current configuration — check its Properties.', 'engine.pageBlockCanvas.itemN': 'Item {n}', @@ -1013,46 +703,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.sourcePageEditor.source': 'Page source', 'engine.sourcePageEditor.renderFailed': 'The page source threw while rendering — fix the code in the Source tab.', - 'engine.validationPreview.empty': 'Give the validation a name to see the preview.', - 'engine.validationPreview.active': 'Active', - 'engine.validationPreview.disabled': 'Disabled', - 'engine.validationPreview.priority': 'priority {priority}', - 'engine.validationPreview.on': 'on {events}', - 'engine.validationPreview.noMessage': 'no message set', - 'engine.validationPreview.tags': 'Tags', - 'engine.validationPreview.removed.unique': - 'Uniqueness is a unique INDEX, not a validation rule: declare it on the object as `indexes: [{ fields: [...], unique: \'organization\' }]` — one holder per organization — or `unique: \'global\'` for one holder across the installation (or `unique: true` on the field). A SELECT-then-INSERT rule is inherently racy (TOCTOU); a database constraint is not.', - 'engine.validationPreview.removed.async': - 'Remote/async validation is a form-layer concern — on the server write path it is an SSRF and latency hazard. Keep it in the form, or enforce the underlying invariant with a unique index or a lifecycle hook.', - 'engine.validationPreview.removed.custom': - 'Arbitrary validation code belongs in a `beforeInsert` / `beforeUpdate` lifecycle hook — the typed, supported extension point.', - 'engine.validationPreview.notARule': 'Not a validation rule', - 'engine.validationPreview.removedType': - '{type} was removed from ValidationRuleSchema — this rule cannot be saved.', - 'engine.validationPreview.condition': 'Condition', - 'engine.validationPreview.when': 'When', - 'engine.validationPreview.thenApply': 'Then apply', - 'engine.validationPreview.noNested': 'No nested rule set — this rule enforces nothing.', - 'engine.validationPreview.otherwiseApply': 'Otherwise apply', - 'engine.validationPreview.transitions': 'Transitions', - 'engine.validationPreview.transitionsOn': 'Transitions on {field}', - 'engine.validationPreview.noTransitions': 'No transitions declared.', - 'engine.validationPreview.deadEnd': '∅ (dead-end)', - 'engine.validationPreview.createdIn': 'May be created in', - 'engine.validationPreview.format': 'Format', - 'engine.validationPreview.formatOn': 'Format on {field}', - 'engine.validationPreview.builtIn': 'Built-in:', - 'engine.validationPreview.regex': 'Regex:', - 'engine.validationPreview.noFormat': 'No format or regex set.', - 'engine.validationPreview.fieldsInvolved': 'Fields involved', - 'engine.validationPreview.none': 'none', - 'engine.validationPreview.crossFieldCondition': 'Cross-field condition', - 'engine.validationPreview.jsonSchemaOn': 'JSON Schema on {field}', - 'engine.validationPreview.noJsonSchema': 'No JSON Schema set.', - 'engine.validationPreview.rule': 'Rule', - 'engine.validationPreview.unknownType': 'Unknown rule type "{type}". Showing common fields only.', - 'engine.validationPreview.noType': 'no type', - 'engine.validationPreview.noExpression': 'No expression set.', 'engine.fieldsListEditor.columns': 'Columns', 'engine.fieldsListEditor.empty': 'No columns yet. Add a field below.', 'engine.fieldsListEditor.colN': 'col {n}', @@ -1168,28 +818,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.quickfind.noMatches': 'No matches.', 'engine.breadcrumb.allTypes': 'All Metadata Types', - // ── StudioDesignSurface (ADR-0080 WYSIWYG design surface) ────────────── - // Shared chrome - 'engine.studio.cancel': 'Cancel', - 'engine.studio.creating': 'Creating…', - // objectui#11792 — CreateItemDialog's preview of the name the item is saved - // under, shown when the caller supplies that rule (an object's namespace prefix). - 'engine.studio.createItem.savedAs': 'Saved as', - 'engine.studio.createItem.notDerived': 'The display name has no Latin letters to make an identifier from. Type one: lowercase letters, digits and underscores.', - // `383502b23` — this is the CONFIRM control of the shared create dialog. - // Never give it a name that shares a leading run with the affordance that - // OPENS that dialog (`engine.studio.app.create`, `Create app` — and the - // dialog itself is titled after it, which is correct). It used to read - // `Create (save as draft)`, so the only handle it offered a by-name caller - // was `Create`, which also matched all three openers (toolbar, Interfaces - // empty state, Interfaces rail): nothing could address THIS control, the one - // that actually creates. While the dialog is open every opener is - // `aria-hidden` behind Radix's modal overlay, so a by-name click there - // dismisses the dialog with no error and nothing created. Pinned by - // `studio-design/StudioDesignSurface.createAppAccessibleName.test.tsx`. - 'engine.studio.createDraft': 'Save as draft', - 'engine.studio.saveDraft': 'Save draft', - 'engine.studio.autoSaving': 'Saving…', // objectui#11773 — the draft-save version conflict (`DraftConflictDialog.tsx`): // a draft save the server refused because the draft was saved elsewhere after // this editor opened it. @@ -1205,259 +833,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.draftConflict.overwriteConfirm': 'Overwrite', 'engine.draftConflict.back': 'Back', 'engine.draftConflict.notSaved': 'Not saved: {type} “{name}” was saved elsewhere after you opened it.', - // Standard create-dialog field labels (shared by object / app / flow / permission). - 'engine.studio.app.nameLabel': 'App name', - 'engine.studio.app.idLabel': 'Identifier', - 'engine.studio.pkg.manage': 'Package info & settings', - // objectui#7881 — the read SUCCEEDED and the package is simply not in the - // list; the sheet is not opened, and this says so instead of nothing. - 'engine.studio.pkg.manageMissing': - 'Package {id} is not in the installed list — it may have been deleted or uninstalled elsewhere.', - // objectui#7907 — the managed-snapshot refresh that follows a lifecycle - // action failed. The sheet is an ACTION surface whose verb is derived from - // the record it holds, so it is closed rather than left re-armed with the - // pre-action one; this names that consequence and carries the server's own - // words inside it (one report, on the shared sonner id). - 'engine.studio.pkg.manageRefreshFailed': - 'Package {id} could not be refreshed after that change — closing the panel rather than showing the pre-change record as current. {error}', - 'engine.studio.publish': 'Publish', - 'engine.studio.loading': 'Loading…', - 'engine.studio.loadFailed': 'Failed to load', - // objectui#11785 — a refused save, in the author's words. The strip shows one - // of these sentences; the raw refusal (field paths, codes) stays under - // `engine.studio.refusal.details`, and `show` opens the input it names. - 'engine.studio.refusal.choiceWithoutOptions': - 'Changes not saved: the field “{field}” needs at least one option. Add an option, or change the field to a type that is not a choice.', - 'engine.studio.refusal.relationshipWithoutTarget': - 'Changes not saved: the field “{field}” does not say which object it links to. Pick the object, or change the field to a type that is not a relationship.', - 'engine.studio.refusal.issue': 'Changes not saved. Check {where} — {problem}', - 'engine.studio.refusal.more': '({count} more under Details)', - 'engine.studio.refusal.unlocated': 'Changes not saved: the server refused this draft. Details say what to change.', - 'engine.studio.refusal.field': 'the field “{field}”', - 'engine.studio.refusal.step': 'the step “{step}”', - 'engine.studio.refusal.stepInput': '{input} on the step “{step}”', - 'engine.studio.refusal.navItem': 'the navigation item “{item}”', - 'engine.studio.refusal.navItemInput': '{input} on the navigation item “{item}”', - 'engine.studio.refusal.details': 'Details', - 'engine.studio.refusal.show': 'Show me', - // objectui#11786 — an edit held, not refused: an incomplete-but-normal body - // (a choice field before its options, a relationship before its target, a - // step before its required inputs) stays on screen unsent. Each `needs*` row - // is a clause the held line and the Publish refusal both build on. - 'engine.studio.held.needsOptions': 'the field “{field}” needs at least one option', - 'engine.studio.held.needsTarget': 'the field “{field}” needs the object it links to', - 'engine.studio.held.needsInput': '{input} on the step “{step}” needs a value', - 'engine.studio.held.needsCondition': 'the rule “{rule}” needs a condition', - 'engine.studio.held.needsThenCondition': 'the “Then” rule of “{rule}” needs a condition', - 'engine.studio.held.needsRuleField': 'the rule “{rule}” needs a field', - 'engine.studio.held.needsRuleMessage': 'the rule “{rule}” needs an error message', - // objectui#11910 — a dashboard widget held until it is bound; `input` is the - // widget inspector's own label for it (Dataset, Values (measures)). - 'engine.studio.held.widgetNeedsInput': '{input} on the widget “{widget}” needs a value', - 'engine.studio.held.line': 'Not saved yet: {clause}. Your changes are kept here and saved once it is filled in.', - 'engine.studio.held.publish': 'Not published: {clause}, so that change is not saved yet. Finish it, then publish.', - 'engine.studio.held.inputHint': 'Required. Changes are saved once this has a value.', - 'engine.studio.unpublishedDraft': 'Unpublished draft', - // objectui#12725 — the item header's draft line: this item has a pending - // draft, and {count} is the package's pending count (the Changes badge's). - 'engine.studio.itemDraftPending': 'Draft · {count} unpublished', - 'engine.studio.itemDraftPendingTitle': - 'This item has an unpublished draft. The package has {count} unpublished change(s); Publish puts them live together.', - 'engine.studio.unpublished': 'Unpublished', - 'engine.studio.new': 'New', - 'engine.studio.edit': 'Edit', - 'engine.studio.done': 'Done', - 'engine.studio.close': 'Close', - 'engine.studio.deselect': 'Clear selection', - 'engine.studio.toggleRail': 'Toggle sidebar', - // objectui#12513 — the header's first button and the package switcher's - // first row: both return to the Studio landing (`/studio`). - 'engine.studio.landingHome': 'Studio home', - // objectui#11795 — the one line a phone shows under the Studio header. - 'engine.studio.desktopHint': 'Studio is built for a desktop screen.', - 'engine.studio.aiCopilot': 'AI copilot', - // objectui#12725 — the Studio right slot: properties OR the copilot. - 'engine.studio.slot.label': 'Right panel', - 'engine.studio.slot.switchHint': 'Switch with ⌘. (Ctrl+.)', - 'engine.studio.slot.collapse': 'Hide the right panel', - 'engine.studio.slot.expand': 'Show the right panel', - 'engine.studio.slot.backToChat': 'Back to the conversation', - 'engine.studio.slot.planPending': 'a plan awaits your approval', - 'engine.studio.slot.openFullPage': 'Open full page', - 'engine.studio.pillar.data': 'Data', - 'engine.studio.pillar.automations': 'Automations', - 'engine.studio.pillar.interfaces': 'Interfaces', - 'engine.studio.pillar.access': 'Access', - // Header · package-level publish + app bridge - 'engine.studio.changes': 'Changes', - 'engine.studio.publishTitle': 'Confirm and publish all pending drafts at once (whole package · one atomic release)', - 'engine.studio.publishNoneTitle': 'No drafts pending publish', - 'engine.studio.publishedAll': 'Published all drafts in this package (one atomic release)', - // objectui#11807 — the publish toast names how many items went live. - 'engine.studio.publishedAllCount': 'Published {count} items in this package (one atomic release)', - 'engine.studio.publishedAllCountOne': 'Published {count} item in this package (one atomic release)', - // objectui#12929 — the batch door's backfill receipt, one clause per field - // the review sheet named: the rows the server wrote, and why a field stopped. - 'engine.studio.backfillWritten': 'Default backfilled into {field}: {count} record(s)', - 'engine.studio.backfillStopped': 'Default backfill of {field} stopped after {count} record(s): {error}', - 'engine.studio.app.open': 'Open app', - 'engine.studio.app.openTitle': 'Open app “{label}” (the published front-end)', - 'engine.studio.app.create': 'Create app', - 'engine.studio.app.noneTitle': 'This package has no app (front-end) yet — create one', - 'engine.studio.app.willOpenAfterPublish': 'After publishing, this becomes “Open app”', - 'engine.studio.app.pending': 'App “{label}” pending publish', - 'engine.studio.app.namePlaceholder': 'App name (e.g. Order Center)', - 'engine.studio.app.idPlaceholder': 'Identifier (e.g. orders_app)', - 'engine.studio.app.savedDraft': 'App “{label}” saved as draft — open it after publishing', - // Package switcher - 'engine.studio.pkg.switchTitle': 'Switch / create package', - 'engine.studio.pkg.readonly': 'Read-only', - 'engine.studio.pkg.readonlyHint': 'Read-only package — switch to or create a writable package to edit.', - // objectui#12453 — the item header's read-only link, every pillar. - 'engine.studio.pkg.readonlyLink': 'Read-only · Customize with an overlay', - 'engine.studio.app.scaffoldNav': "Add this package's objects as menu items", - 'engine.studio.landing.title': 'App Builder', - 'engine.studio.landing.description': 'Design objects, forms, automations and interfaces inside a writable package; edits save as drafts and the whole package publishes in one release. Code and installed packages are read-only: browse them here, and customize them with an environment overlay.', - 'engine.studio.landing.mineHeading': 'My packages (writable)', - 'engine.studio.landing.noneWritable': 'No writable packages yet — create one to start.', - 'engine.studio.landing.dup': 'Duplicate', - 'engine.studio.landing.dupTitle': 'Duplicate this package into a new writable copy (like “duplicate base”)', - 'engine.studio.landing.dupDefaultName': '{name} copy', - 'engine.studio.landing.dupNamePlaceholder': 'Copy name', - 'engine.studio.landing.dupIdPlaceholder': 'Copy package ID', - 'engine.studio.landing.dupGo': 'Duplicate and open the builder', - 'engine.studio.landing.dupCreated': 'Duplicated into writable package “{name}”', - 'engine.studio.landing.installedHeading': 'Installed (read-only · browsable)', - // objectui#11808 — the routes a read-only package does have (ADR-0070 D2, - // ADR-0005): an environment overlay, and the marketplace where the runtime has one. - 'engine.studio.landing.readonlyHint': 'Code and installed packages are read-only, so they are not edited or duplicated here. Customize one with an environment overlay, for the metadata types that accept one.', - // objectui#12513 — the link leaves Studio for Setup, and says so. - 'engine.studio.landing.overlay': 'Open in Setup', - 'engine.studio.landing.overlayTitle': 'Open this package’s metadata: each type shows whether it accepts an environment overlay', - 'engine.studio.landing.marketplace': 'Or install a template app from the marketplace', - // objectui#11553 — the package-less scope: the organization's own flows. - 'engine.studio.landing.orgHeading': 'Not in a package', - 'engine.studio.org.name': 'Organization flows', - 'engine.studio.org.hint': 'Flows that belong to no package', - 'engine.studio.org.description': 'The organization’s own flows that belong to no package, such as a clone of a packaged flow. They open editable.', - 'engine.studio.org.none': 'No flows outside a package yet. Clone a packaged flow in Setup › Packaged automation to edit the copy here.', - // objectui#12513 — the empty block's way to that Setup list: a link, since this scope creates nothing. - 'engine.studio.org.openSetup': 'Open in Setup', - 'engine.studio.publishedAllFlows': 'Published {count} package-less flow drafts', - 'engine.studio.publishedAllFlowsOne': 'Published {count} package-less flow draft', - 'engine.studio.designer.search': 'Search…', - 'engine.studio.designer.select': 'Select…', - 'engine.studio.designer.pickDate': 'Pick a date…', - 'engine.studio.designer.fieldAria': '{label} — click to edit properties, drag to reorder', - // objectui#11781 — a read-only package's card only opens the greyed inspector. - 'engine.studio.designer.fieldAriaReadOnly': '{label} — click to view properties', - // objectui#11872 — a field card's `aria-roledescription`: the role read after - // its label, in place of dnd-kit's English default `sortable`. - 'engine.studio.designer.fieldRole': 'draggable field', - 'engine.studio.designer.groupUp': 'Move group up', - 'engine.studio.designer.groupDown': 'Move group down', - 'engine.studio.designer.groupDelete': 'Delete group', - 'engine.studio.designer.groupDeleteTitle': 'Delete group (its fields move to Ungrouped)', - 'engine.studio.designer.dropHere': 'Drag fields here', - 'engine.studio.designer.ungrouped': 'Ungrouped', - 'engine.studio.designer.newGroup': 'New group', - // objectui#12453 — a read-only field card's title: what a click still does. - 'engine.studio.designer.fieldTitleReadOnly': 'Click a field to view its properties', - // objectui#12451 — the Form tab toolbar's hint. - 'engine.studio.designer.toolbarHint': 'Drag fields to arrange · click a field to edit its properties', - 'engine.studio.designer.addGroup': 'Add group', - // What the form designer's drag live region says (objectui#11802): labels, - // never the canvas's internal ids, and the place as "N of M" in the group. - 'engine.studio.formDnd.instructions': - 'To pick up a field, press Space or Enter. While dragging, use the arrow keys to move it. Press Space or Enter again to drop it in its new place, or press Escape to cancel.', - 'engine.studio.formDnd.start': 'Picked up {field}. It is in {group}, position {position} of {total}.', - 'engine.studio.formDnd.over': '{field} is over {group}, position {position} of {total}.', - 'engine.studio.formDnd.overNone': '{field} is not over a group.', - 'engine.studio.formDnd.end': '{field} moved to {group}, position {position} of {total}.', - 'engine.studio.formDnd.endNone': '{field} was dropped outside the groups and is back in {group}, position {position} of {total}.', - 'engine.studio.formDnd.cancel': 'Dragging cancelled. {field} is back in {group}, position {position} of {total}.', - 'engine.studio.rules.title': 'Validation rules', - 'engine.studio.rules.none': 'No validation rules yet.', - 'engine.studio.rules.newButton': 'New validation rule', - 'engine.studio.rules.noMessage': '(no message)', - 'engine.studio.rules.notSaved': 'Not saved — needs a condition', - 'engine.studio.rules.notSavedField': 'Not saved — needs a field', - 'engine.studio.rules.notSavedMessage': 'Not saved — needs a message', - 'engine.studio.rules.structured': '“{type}” rules carry structured configuration (state-machine transitions / format constraints etc.) and cannot be edited here yet — maintain them in the code package. Message: {message}', - 'engine.studio.rules.none2': '(none)', - 'engine.studio.rules.deleteRule': 'Delete rule', - 'engine.studio.rules.nameLabel': 'Rule name (snake_case, unique)', - 'engine.studio.rules.messageLabel': 'Error message (shown to the user when validation fails)', - 'engine.studio.rules.messagePlaceholder': 'e.g. Completion date is required when status is Done', - 'engine.studio.rules.celPre': 'Fail condition (CEL) — when the condition is ', - 'engine.studio.rules.celTrue': 'true', - 'engine.studio.rules.celMid': ', the save is rejected with the message above.', - // A conditional rule's `when`: it says when the `then` rule applies. - 'engine.studio.rules.whenPre': 'Guard (CEL) — when the condition is ', - 'engine.studio.rules.whenMid': ', the “Then” rule below applies; otherwise the “Otherwise” rule, if one is set.', - 'engine.studio.rules.severity': 'Severity', - 'engine.studio.rules.severityError': 'error (rejects the save)', - 'engine.studio.rules.enabled': 'Enabled', - 'engine.studio.rules.delete': 'Delete', - 'engine.studio.rules.type': 'Type', - 'engine.studio.rules.newType': 'New validation type', - 'engine.studio.rules.typeScript': 'Script — CEL fail condition', - 'engine.studio.rules.typeCrossField': 'Cross-field — CEL over multiple fields', - 'engine.studio.rules.typeStateMachine': 'State machine — allowed transitions', - 'engine.studio.rules.typeFormat': 'Format — regex / built-in format', - 'engine.studio.rules.typeJsonSchema': 'JSON schema — validate a JSON field', - 'engine.studio.rules.typeConditional': 'Conditional — apply a rule when a guard holds', - 'engine.studio.rules.label': 'Label (optional)', - 'engine.studio.rules.field': 'Field', - 'engine.studio.rules.statusField': 'Status field', - 'engine.studio.rules.fields': 'Participating fields', - 'engine.studio.rules.noFields': 'This object has no fields yet.', - 'engine.studio.rules.pickField': '— pick a field —', - 'engine.studio.rules.format': 'Built-in format', - 'engine.studio.rules.formatNone': '— none —', - 'engine.studio.rules.regex': 'Regex pattern', - 'engine.studio.rules.transitions': 'Allowed transitions (from → next states)', - 'engine.studio.rules.transitionFrom': 'From state', - 'engine.studio.rules.transitionTo': 'Next states (comma-separated)', - 'engine.studio.rules.addTransition': 'Add transition', - 'engine.studio.rules.jsonSchema': 'JSON Schema', - 'engine.studio.rules.invalidJson': 'Invalid JSON', - 'engine.studio.rules.then': 'Then — rule applied when the condition holds (JSON)', - 'engine.studio.rules.otherwise': 'Otherwise — rule applied when it does not (JSON, optional)', - 'engine.studio.rules.events': 'Runs on', - 'engine.studio.rules.event.insert': 'Create', - 'engine.studio.rules.event.update': 'Update', - 'engine.studio.rules.priority': 'Priority', - // objectui#11861 — the New menu's starting points, and the type list under Advanced. - 'engine.studio.rules.presets': 'Common rules', - 'engine.studio.rules.advanced': 'Advanced', - 'engine.studio.rules.preset.endAfterStart': 'End date on or after start date', - 'engine.studio.rules.preset.endAfterStartNeeds': 'Needs two date fields on this object.', - 'engine.studio.rules.preset.endAfterStartMessage': '{end} must be on or after {start}.', - 'engine.studio.rules.preset.notNegative': 'Number can’t be negative', - 'engine.studio.rules.preset.notNegativeNeeds': 'Needs a number field on this object.', - 'engine.studio.rules.preset.notNegativeMessage': '{field} can’t be negative.', - 'engine.studio.rules.preset.rejectWhen': 'Reject the save when…', - 'engine.studio.rules.preset.rejectWhenHint': 'Build your own condition, no code needed.', - 'engine.studio.rules.preset.usesOne': 'Uses “{field}”.', - 'engine.studio.rules.preset.usesTwo': 'Uses “{start}” and “{end}”.', - 'engine.studio.pkg.writable': 'Writable', - 'engine.studio.pkg.heading': 'Packages (apps)', - 'engine.studio.pkg.none': 'No app packages yet', - 'engine.studio.pkg.createWritable': 'Create writable package', - 'engine.studio.pkg.new': 'New package (writable base)', - 'engine.studio.pkg.created': 'Package {name} created (writable)', - // Nav item inspector (Interfaces pillar) - 'engine.studio.nav.selectItem': 'Select a menu item on the left.', - 'engine.studio.nav.label': 'Label', - 'engine.studio.nav.linkObject': 'Link to object', - 'engine.studio.nav.chooseObject': '— Choose object —', - 'engine.studio.nav.boundHint': 'This menu item opens that object’s record list.', - 'engine.studio.nav.unboundHint': 'Choose an object; the menu item will open its record list.', - 'engine.studio.nav.noObjects': 'This package has no objects yet — create one in the Data pillar first.', - 'engine.studio.nav.eyebrow': 'Nav item · {kind}', - 'engine.studio.nav.icon': 'Icon', - 'engine.studio.nav.visibleWhen': 'Visible when', 'engine.studio.inspector.props': 'Properties', 'engine.studio.inspector.draftFooter': 'Edits save as drafts. Publish the package to apply them.', 'engine.studio.inspector.collapse': 'Collapse properties', @@ -1506,129 +881,10 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.inspector.listView.kanbanCard.mode.none': "Don't open", 'engine.studio.inspector.listView.kanbanCard.groupBy': 'Group by field', 'engine.studio.inspector.listView.kanbanCard.pickField': 'Pick a field', - // objectui#12725 — the copilot's draft marks on a field or a view setting it changed. - 'engine.studio.draftMark.chip': 'Draft', - 'engine.studio.draftMark.sourceLabel': 'Source', - 'engine.studio.draftMark.source': 'Added as a draft by the AI copilot {when}', - 'engine.studio.draftMark.sourceJustNow': 'Added as a draft by the AI copilot just now', - 'engine.studio.draftMark.sourceItems': '{items}: {count} added as a draft by the AI copilot {when}', - 'engine.studio.draftMark.sourceItemsJustNow': '{items}: {count} added as a draft by the AI copilot just now', - 'engine.studio.draftMark.item': '"{label}"', - 'engine.studio.draftMark.minutesAgo': '{n} min ago', - 'engine.studio.draftMark.hoursAgo': '{n} h ago', - 'engine.studio.draftMark.daysAgo': '{n} d ago', - // objectui#11823 (steps 2 and 3) — the Interfaces pillar's New dashboard / New report / New page. - 'engine.studio.interfaces.create.menuTitle': 'Create a dashboard, a report or a page in this package', - 'engine.studio.interfaces.create.dashboard': 'New dashboard', - 'engine.studio.interfaces.create.report': 'New report', - 'engine.studio.interfaces.create.description': - 'Saved as a draft in this package, linked from the navigation of {app}, and opened on the canvas.', - 'engine.studio.interfaces.create.dashboardNameLabel': 'Dashboard name', - 'engine.studio.interfaces.create.reportNameLabel': 'Report name', - 'engine.studio.interfaces.create.dashboardNamePlaceholder': 'Dashboard name (e.g. Sales overview)', - 'engine.studio.interfaces.create.reportNamePlaceholder': 'Report name (e.g. Revenue by region)', - 'engine.studio.interfaces.create.idLabel': 'Identifier', - 'engine.studio.interfaces.create.dashboardIdPlaceholder': 'Identifier (e.g. sales_overview)', - 'engine.studio.interfaces.create.reportIdPlaceholder': 'Identifier (e.g. revenue_by_region)', - 'engine.studio.interfaces.create.dashboardTaken': - 'A dashboard with the identifier “{name}” already exists. Choose another identifier.', - 'engine.studio.interfaces.create.reportTaken': - 'A report with the identifier “{name}” already exists. Choose another identifier.', - 'engine.studio.interfaces.create.dataset': 'Dataset', - 'engine.studio.interfaces.create.datasetPlaceholder': 'Choose a dataset…', - 'engine.studio.interfaces.create.measure': 'Measure', - 'engine.studio.interfaces.create.measurePlaceholder': 'Choose a measure…', - 'engine.studio.interfaces.create.reportHint': - 'A report shows measures of one dataset. Add more measures and groupings in the Properties panel once it is created.', - 'engine.studio.interfaces.create.noDatasets': - 'There is no dataset to report on yet. A report shows the measures of a dataset, so create a dataset first.', - 'engine.studio.interfaces.create.datasetsFailed': 'The datasets could not be loaded: {error}', - 'engine.studio.interfaces.create.noMeasures': 'This dataset declares no measures, so a report cannot show it.', - 'engine.studio.interfaces.create.needsBinding': 'Choose the dataset this report reads, and one of its measures.', - 'engine.studio.interfaces.create.page': 'New page', - 'engine.studio.interfaces.create.pageNameLabel': 'Page name', - 'engine.studio.interfaces.create.pageNamePlaceholder': 'Page name (e.g. Team handbook)', - 'engine.studio.interfaces.create.pageIdPlaceholder': 'Identifier (e.g. team_handbook)', - 'engine.studio.interfaces.create.pageTaken': - 'A page with the identifier “{name}” already exists. Choose another identifier.', - 'engine.studio.interfaces.create.pageKind': 'Written in', - 'engine.studio.interfaces.create.pageKindHtml': 'HTML', - 'engine.studio.interfaces.create.pageKindReact': 'React', - 'engine.studio.interfaces.create.pageKindHtmlHint': - 'Built from the platform’s components and HTML tags, parsed and never run. The page opens on its source beside a live preview.', - 'engine.studio.interfaces.create.pageKindReactHint': - 'Real React code, run when the page renders, for state, effects and event handlers. The page opens on its source beside a live preview.', - 'engine.studio.interfaces.create.pageStarter': 'A new page. Write its content in the source editor.', 'engine.studio.inspector.designersMissing': 'No metadata designers are registered in this session, so there is nothing to edit here.', 'engine.studio.inspector.noPageSchema': 'Page settings are unavailable — the page schema could not be loaded.', - // Actions view - 'engine.studio.actions.none': 'No actions on this object.', - 'engine.studio.actions.newButton': 'New action', - 'engine.studio.actions.editorMissing': - 'No action editor is registered in this session, so this action’s properties cannot be edited here.', - 'engine.studio.actions.newLabel': 'New action', - 'engine.studio.actions.delete': 'Delete', - // objectui#11861 — the New menu's starting points (`actionPresets.ts`). - 'engine.studio.actions.presets': 'Common actions', - 'engine.studio.actions.notSaved': 'Not saved — needs “{input}”', - 'engine.studio.actions.heldNeeds': 'the action “{action}” needs “{input}”', - 'engine.studio.actions.preset.change': 'Change a picklist field', - 'engine.studio.actions.preset.changeHint': 'Asks for a new “{field}”, then saves it.', - 'engine.studio.actions.preset.changeNeeds': 'Needs a picklist field on this object.', - 'engine.studio.actions.preset.changeLabel': 'Change {field}', - 'engine.studio.actions.preset.runFlow': 'Run a flow', - 'engine.studio.actions.preset.openUrl': 'Open a web page', - 'engine.studio.actions.preset.openPage': 'Open a page in a dialog', - // API view - 'engine.studio.api.subtitle': - 'Auto-generated REST endpoints for this object, derived from its current schema. Copy a request as cURL and call it with your API token.', - 'engine.studio.api.list': 'List records', - 'engine.studio.api.create': 'Create a record', - 'engine.studio.api.get': 'Get one by id', - 'engine.studio.api.update': 'Update a record', - 'engine.studio.api.delete': 'Delete a record', - 'engine.studio.api.copyCurl': 'Copy as cURL', - 'engine.studio.api.copied': 'Copied', - 'engine.studio.api.body': 'Example body', - 'engine.studio.api.bodyOmitted': 'Required fields come first. Optional fields not shown: {fields}', - // Hooks view - 'engine.studio.hooks.renameHeld': 'Not saved yet: a hook keeps the name it was created with. Change the name back to {name} to save your changes.', - 'engine.studio.hooks.none': 'No hooks target this object.', - 'engine.studio.hooks.newButton': 'New hook', - 'engine.studio.hooks.async': 'async', - 'engine.studio.hooks.pick': 'Select a hook to edit it.', - 'engine.studio.hooks.newLabel': 'New hook', - 'engine.studio.designer.group.kind': 'Group', - 'engine.studio.designer.group.settings': 'Group settings', - 'engine.studio.designer.group.nameLabel': 'Group name', - 'engine.studio.designer.group.missing': 'This group no longer exists.', - 'engine.studio.designer.group.collapseLabel': 'Collapse behavior', - 'engine.studio.designer.group.collapseNone': 'Not collapsible', - 'engine.studio.designer.group.collapseExpanded': 'Collapsible · expanded by default', - 'engine.studio.designer.group.collapseCollapsed': 'Collapsible · collapsed by default', - 'engine.studio.designer.group.collapseHint': 'Controls how this group appears in the actual form (see Preview).', - // objectui#11788 — the New automation dialog's trigger choice, left unset. - 'engine.studio.newAutoTrigger.later': 'Choose later on the Start node', - // ── Record Sharing Baseline (OWD) overview (objectui#2505) ────────────── - 'engine.studio.owd.railLabel': 'Record sharing (OWD)', - 'engine.studio.owd.title': 'Record sharing baseline (OWD)', - 'engine.studio.owd.subtitle': 'Object × org-wide default', - 'engine.studio.owd.description': - 'The org-wide default (sharingModel) for every object this package owns — audit the baseline at a glance and batch-edit it. Changes save as per-object drafts and go live through the package Publish, exactly like the per-object Settings tab.', - 'engine.studio.owd.colObject': 'Object', - 'engine.studio.owd.colInternal': 'Sharing model (sharingModel)', - 'engine.studio.owd.none': 'This package declares no objects.', - 'engine.studio.owd.loadFailed': 'Failed to load this package’s objects.', - 'engine.studio.owd.save': 'Save {count} change(s)', - 'engine.studio.owd.saveNone': 'No changes', - 'engine.studio.owd.saved': 'Saved {count} object draft(s).', - 'engine.studio.owd.unsaved': 'Unsaved', - 'engine.studio.owd.byParent': 'By parent → {master}', - 'engine.studio.owd.byParentNoMaster': 'By parent — no master: the object needs exactly one required master-detail field', - 'engine.studio.owd.masterTip': - 'Controlled by parent — record visibility is inherited from the master record.', // ── AppNavCanvas (nav-tree editor, used by Studio + AppPreview) ───────── @@ -1805,16 +1061,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.widgetPicker.type.funnel': '漏斗图', 'engine.widgetPicker.type.table': '数据表格', 'engine.widgetPicker.type.pivot': '透视表', - // objectui#10835 — the dashboard designer's preview canvas (see the en rows). - 'engine.dashboardPreview.empty': '至少添加一个组件才能查看预览。', - 'engine.dashboardPreview.malformed': '某个组件引用了无法解析的对象或字段。', - 'engine.dashboardPreview.loading': '正在加载仪表盘渲染器…', - 'engine.dashboardPreview.selected': '已选中', - 'engine.dashboardPreview.clickToRename': '点击重命名', - 'engine.dashboardPreview.untitled': '未命名', - 'engine.dashboardPreview.saveTitle': '保存标题', - 'engine.dashboardPreview.renameWidget': '重命名组件', - 'engine.dashboardPreview.clearSelection': '取消选择', // ConditionBuilder 裸表达式模式(CEL 编辑器,#1582) 'engine.condition.celLabel': 'CEL 表达式', 'engine.condition.advancedHint': '高级表达式 —— 搭建器仅支持简单的 AND/OR 条件。', @@ -2027,155 +1273,8 @@ const ENGINE_STRINGS_ZH: Record = { // objectui#10848 — the CEL failure frames and the preview chrome (see the en rows). 'engine.flowSim.note.celEvaluationFailed': 'CEL 求值失败:{message}', 'engine.flowSim.note.celConditionFailed': '条件无法按 CEL 求值:{message}', - 'engine.previewShell.renderFailed': '预览渲染失败', - 'engine.screenPreview.header': '预览', - 'engine.screenPreview.empty': '添加标题、描述、字段或对象表单,即可预览此交互页面。', - 'engine.screenPreview.submit': '提交', - 'engine.screenPreview.noDataSource': '连接后端后即可预览此对象表单。', - 'engine.screenPreview.hiddenOne': '有 {count} 个字段因其“显示条件”而隐藏。', - 'engine.screenPreview.hiddenOther': '有 {count} 个字段因其“显示条件”而隐藏。', - 'engine.screenPreview.defaultTemplate': '“{field}”的默认值模板,按原文显示 —— 运行时填入实际值:', - // objectui#10862 — the flow / automation previews and the CEL try-it dialog (see the en rows). - 'engine.actionPreview.empty': '设置名称和标签,即可查看操作预览。', - 'engine.actionPreview.pill.type': '类型:{type}', - 'engine.actionPreview.pill.object': '对象:{object}', - 'engine.actionPreview.pill.variant': '样式:{variant}', - 'engine.actionPreview.pill.refreshAfter': '执行后刷新', - 'engine.actionPreview.pill.aiOptedOut': 'AI:不开放', - 'engine.actionPreview.pill.aiExposed': 'AI:已开放', - 'engine.actionPreview.locations': '位置:', - 'engine.actionPreview.requires': '需要:', - 'engine.actionPreview.visibleWhen': '可见条件', - 'engine.actionPreview.disabledWhen': '禁用条件', - 'engine.actionPreview.onClick': '点击时', - 'engine.actionPreview.firstAsks': '先询问:', - 'engine.actionPreview.onSuccess': '成功后:', - 'engine.actionPreview.handler.none': '尚未绑定处理程序。', - 'engine.actionPreview.handler.url': '跳转到 {target}', - 'engine.actionPreview.handler.flow': '运行流程 {target}', - 'engine.actionPreview.handler.modal': '打开弹窗 {target}', - 'engine.actionPreview.handler.api': '调用 API 端点 {target}', - 'engine.actionPreview.handler.form': '打开表单视图 {target}(/console/forms/{path})', - 'engine.actionPreview.handler.scriptBody': '运行内联脚本体(L1 表达式或 L2 沙箱 JS)。', - 'engine.actionPreview.handler.scriptNamed': '运行具名脚本 {target}', - 'engine.actionPreview.handler.invoke': '调用 {target}', - 'engine.actionPreview.section.placement': '显示位置', - 'engine.actionPreview.capabilityGateNote': - '不具备 {capabilities} 的用户看不到它 —— 下方的框架展示的是摆放位置,而不是谁能看到它。', - 'engine.actionPreview.section.testRequest': '测试请求', - 'engine.actionPreview.section.inputDialog': '输入对话框', - 'engine.actionPreview.section.resultDialog': '结果对话框', - 'engine.actionPreview.section.scriptBody': '脚本体({language})', - 'engine.actionPreview.dialog.ok': '确定', - 'engine.actionPreview.param.carryOver': '沿用所选行的值(只读)', - 'engine.actionPreview.param.fromRow': '(来自所选行)', - 'engine.actionPreview.param.selectPlaceholder': '选择 {label}', - 'engine.actionPreview.param.recordIdPlaceholder': '{label} 的记录 ID', - 'engine.actionPreview.param.noReference': - '未配置 {reference} 对象,因此记录选择器不可用,对话框会改为要求输入记录 ID。', - 'engine.actionPreview.param.toggle': '开关', - 'engine.actionPreview.param.noChoices': '尚未编写选项 —— 对话框会打开一个空的选择器。', - 'engine.actionPreview.param.searchObject': '搜索 {object}…', - 'engine.actionPreview.param.searchRecords': '搜索记录…', - 'engine.actionPreview.result.title': '结果', - 'engine.actionPreview.result.acknowledge': '我已保存', - 'engine.actionPreview.result.fullJson': '显示完整的 JSON 响应。', - 'engine.actionPreview.placement.sampleRecord': '示例记录', - 'engine.actionPreview.placement.recordDetail': '记录详情', - 'engine.actionPreview.placement.recordBody': '…记录正文…', - 'engine.actionPreview.placement.records': '记录', - 'engine.actionPreview.placement.rows': '第 1 行 · 第 2 行 · 第 3 行', - 'engine.actionPreview.placement.row': '第 {n} 行', - 'engine.actionPreview.placement.section': '分区', - 'engine.actionPreview.placement.sectionBody': '…分区内容…', - 'engine.actionPreview.placement.more': '更多', - 'engine.actionPreview.api.paramsOne': '+ {count} 个由用户在运行时提供的参数', - 'engine.actionPreview.api.paramsOther': '+ {count} 个由用户在运行时提供的参数', - 'engine.actionPreview.api.liveWarning': '会向在线后端发送真实的 {method} 请求 —— 可能修改数据。', - 'engine.actionPreview.api.sending': '发送中…', - 'engine.actionPreview.api.send': '发送测试请求', - 'engine.actionPreview.api.networkError': '网络错误', - 'engine.agentPreview.tryInChat': '在对话中试用', - 'engine.agentPreview.tryInChatTitle': '在新对话中打开此智能体的已保存版本', - 'engine.agentPreview.empty': '在“表单”标签页中填写标签、角色和指令,即可查看智能体预览。', - 'engine.agentPreview.active': '已启用', - 'engine.agentPreview.disabled': '已停用', - 'engine.agentPreview.temperature': '温度 {value}', - 'engine.agentPreview.maxTokens': '上限 {count} token', - 'engine.agentPreview.instructions': '指令', - 'engine.agentPreview.noInstructions': '尚未设置系统提示词。', - 'engine.agentPreview.capabilities': '能力', - 'engine.agentPreview.skills': '技能', - 'engine.agentPreview.noSkills': '未关联技能 —— 此智能体无法使用任何工具。', - 'engine.agentPreview.toolsNote': '工具来自所关联的技能;知识依据在指令中描述。', - 'engine.agentPreview.planning': '规划', - 'engine.agentPreview.memory': '记忆', - 'engine.agentPreview.guardrails': '护栏', - 'engine.agentPreview.permissions': '权限', - 'engine.agentPreview.defaults': '使用默认设置。', - 'engine.agentPreview.noValues': '未设置任何值。', - 'engine.skillPreview.empty': '为技能命名,并至少提供一段指令,即可查看预览。', - 'engine.skillPreview.active': '已启用', - 'engine.skillPreview.disabled': '已停用', - 'engine.skillPreview.model': '模型:{model}', - 'engine.skillPreview.instructions': '指令', - 'engine.skillPreview.noInstructions': '尚无指令 —— 此技能不会向提示词贡献任何内容。', - 'engine.skillPreview.tools': '工具({count})', - 'engine.skillPreview.noTools': '未加入任何白名单工具。', - 'engine.skillPreview.wildcardNote': '通配符在运行时会展开为许多工具 —— 启用前请在智能体中核对匹配到的工具集。', - 'engine.skillPreview.triggerConditions': '触发条件({count})', - 'engine.skillPreview.col.field': '字段', - 'engine.skillPreview.col.operator': '运算符', - 'engine.skillPreview.col.value': '值', - 'engine.skillPreview.allMustHold': '所有条件都必须成立(AND),技能才会激活。', - 'engine.skillPreview.missing': '缺失', - 'engine.skillPreview.missingTitle': 'SkillTriggerConditionSchema 要求此项 —— 此草稿保存时会被拒绝。', - 'engine.toolPreview.openApiConsole': '在 API 控制台中打开', - 'engine.toolPreview.openApiConsoleTitle': '打开 API 控制台以测试调用', - 'engine.toolPreview.empty': '设置名称、描述和参数,即可查看工具预览。', - 'engine.toolPreview.inputParameters': '输入参数', - 'engine.toolPreview.noParameters': '此工具不接受输入参数。', - 'engine.toolPreview.exampleCall': 'LLM 调用示例', - 'engine.toolPreview.outputSchema': '输出结构', - 'engine.toolPreview.col.name': '名称', - 'engine.toolPreview.col.type': '类型', - 'engine.toolPreview.col.description': '描述', - 'engine.toolPreview.required': '必填', - 'engine.toolPreview.enum': '枚举:{values}', - 'engine.toolPreview.default': '默认:', - 'engine.toolPreview.format': '格式:{format}', - 'engine.jobPreview.empty': '设置调度(cron / every / at)和处理程序,即可查看后台任务预览。', - 'engine.jobPreview.active': '已启用', - 'engine.jobPreview.paused': '已暂停', - 'engine.jobPreview.retries': '重试:{count}', - 'engine.jobPreview.retriesBackoff': '重试:{count}(退避 {backoff})', - 'engine.jobPreview.timeout': '超时:{duration}', - 'engine.jobPreview.concurrency': '并发:{count}', - 'engine.jobPreview.schedule': '调度', - 'engine.jobPreview.every': '间隔', - 'engine.jobPreview.at': '时刻', - 'engine.jobPreview.unparseable': '无法解析', - 'engine.jobPreview.noSchedule': '未设置调度 —— 仅在手动触发时运行。', - 'engine.jobPreview.nextRunOne': '接下来 {count} 次运行', - 'engine.jobPreview.nextRunOther': '接下来 {count} 次运行', - 'engine.jobPreview.noFireTime': '无法推算出任何未来的触发时间。请检查调度语法。', - 'engine.jobPreview.noUpcoming': '未来 366 天内没有待运行的计划。', - 'engine.jobPreview.inDelta': '{delta} 后', - 'engine.jobPreview.inNow': '即刻', - 'engine.jobPreview.handler': '处理程序', - 'engine.jobPreview.noHandler': '未绑定处理程序 —— 此任务不会执行任何操作。', 'engine.celTest.predicateEmpty': '条件为空。', 'engine.celTest.notObject': '应为 JSON 对象。', - // objectui#10862 (slice 2) — the admin previews and the CEL lint advisories (see the en rows). - 'engine.appPreview.openTitle': '在新标签页中打开此应用', - 'engine.appPreview.open': '打开', - 'engine.appPreview.homeFirst': '首页:打开第一个导航项', - 'engine.appPreview.homeNone': '首页:尚无可生成路由的导航项', - 'engine.appPreview.empty': '没有顶层导航项。在“表单”标签页中添加 {navigation} 条目,即可填充应用的导航。', - 'engine.appPreview.unnamed': '(未命名)', - 'engine.appPreview.noTypeTitle': '每个导航项都需要 `type` 判别字段 —— 此条目将在保存时被拒绝。', - 'engine.appPreview.noType': '无类型', - 'engine.appPreview.areas': '区域', 'engine.bookPreview.nameTitle': '为文档书命名', 'engine.bookPreview.nameDescription': '在“表单”标签页中输入名称,即可开始编写文档目录骨架。', 'engine.bookPreview.treeTitle': '打开实时解析的导航树(每个分组的实际文档)', @@ -2226,74 +1325,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.docPreview.source': 'Markdown 源码', 'engine.docPreview.sourceLocale': 'Markdown 源码({locale})', 'engine.docPreview.sourcePlaceholder': '# 标题\n\n在此编写 Markdown…', - 'engine.emailTemplatePreview.empty': '在“表单”标签页中填写主题 / 正文,即可查看预览。', - 'engine.emailTemplatePreview.emptyBody': '(正文为空)', - 'engine.emailTemplatePreview.from': '发件人', - 'engine.emailTemplatePreview.to': '收件人', - 'engine.emailTemplatePreview.subject': '主题', - 'engine.emailTemplatePreview.frameTitle': '邮件预览', - 'engine.emailTemplatePreview.variables': '变量', - 'engine.emailTemplatePreview.noVariables': '未找到 {var} 占位符。', - 'engine.emailTemplatePreview.samplePlaceholder': '{name} 的示例值', - 'engine.permissionPreview.empty': '至少授予一项对象、系统或标签页权限,即可查看权限矩阵。', - 'engine.permissionPreview.kind.default': '默认权限集', - 'engine.permissionPreview.kind.set': '权限集', - 'engine.permissionPreview.pill.objects': '{count} 个对象', - 'engine.permissionPreview.pill.systemPerms': '{count} 项系统权限', - 'engine.permissionPreview.pill.tabs': '{count} 个标签页', - 'engine.permissionPreview.pill.rls': '{count} 条 RLS 规则', - 'engine.permissionPreview.section.objects': '对象权限', - 'engine.permissionPreview.section.fields': '字段级覆盖', - 'engine.permissionPreview.section.system': '系统权限', - 'engine.permissionPreview.section.tabs': '标签页可见性', - 'engine.permissionPreview.col.object': '对象', - 'engine.permissionPreview.cap.create': '创建', - 'engine.permissionPreview.cap.read': '读取', - 'engine.permissionPreview.cap.edit': '编辑', - 'engine.permissionPreview.cap.delete': '删除', - 'engine.permissionPreview.cap.export': '导出', - 'engine.permissionPreview.cap.transfer': '转移', - 'engine.permissionPreview.cap.viewAll': '查看全部', - 'engine.permissionPreview.cap.modifyAll': '修改全部', - 'engine.permissionPreview.col.scopeTitle': '读 / 写访问深度(ADR-0057)', - 'engine.permissionPreview.col.scope': '范围', - 'engine.permissionPreview.readScope': '读取范围', - 'engine.permissionPreview.writeScope': '写入范围', - 'engine.permissionPreview.scopeDefault': '默认', - 'engine.permissionPreview.fls.hidden': '隐藏', - 'engine.permissionPreview.fls.editable': '可编辑', - 'engine.permissionPreview.sanityOne': '{count} 项合理性检查未通过', - 'engine.permissionPreview.sanityOther': '{count} 项合理性检查未通过', - 'engine.permissionPreview.warn.editWithoutRead': '授予了编辑却未授予读取(记录更新将失败)。', - 'engine.permissionPreview.warn.deleteWithoutRead': '授予了删除却未授予读取。', - 'engine.permissionPreview.warn.modifyAllWithoutViewAll': '授予了修改全部却未授予查看全部 —— 修改可能作用于不可见的记录。', - 'engine.permissionPreview.warn.writeWiderThanRead': '写入范围({write})比读取范围({read})更宽 —— 编辑可能作用于不可见的记录。', - 'engine.permissionPreview.granted': '已授予', - 'engine.permissionPreview.notGranted': '未授予', - 'engine.permissionPreview.legend.bypass': '绕过共享(查看/修改全部)', - 'engine.permissionPreview.legend.key': 'C R U D = 增删改查 · E T = 导出/转移 · V* M* = 查看/修改全部', - 'engine.positionPreview.empty': '为岗位命名,即可查看预览。', - 'engine.positionPreview.noAccess': '岗位本身不授予数据访问权限。', - 'engine.positionPreview.bind': - '将 {name} 绑定到一个或多个 {permissionSets},以控制 CRUD-VAMA、字段访问和标签页可见性。记录的可见性来自业务单元树和共享规则,而不是岗位。', - 'engine.positionPreview.permissionSets': '权限集', - // objectui#13304 — a position that declares `permissionSets` (zh). - 'engine.positionPreview.grantsVia': '该岗位通过以下{permissionSets}授予访问权限:', - 'engine.positionPreview.visibility': '记录的可见性来自业务单元树和共享规则,而不是岗位。', - // objectui#10202 — the read-only picklist page (zh). - 'engine.picklistPreview.empty': '该选项列表没有名称。', - 'engine.picklistPreview.readOnly': '只读:选项列表归声明它的软件包所有,不能在这里新建、编辑或删除。', - 'engine.picklistPreview.ownedBy': '所属软件包', - 'engine.picklistPreview.options': '选项', - 'engine.picklistPreview.colLabel': '标签', - 'engine.picklistPreview.colValue': '取值', - 'engine.picklistPreview.noOptions': '该选项列表没有声明任何选项。', - 'engine.picklistPreview.extensions': '扩展', - 'engine.picklistPreview.extensionsHint': '其他软件包为该选项列表追加的选项。', - 'engine.picklistPreview.addedBy': '由 {package} 追加', - 'engine.picklistPreview.noExtensions': '没有软件包扩展该选项列表。', - 'engine.picklistPreview.extensionsLoading': '正在加载扩展…', - 'engine.picklistPreview.extensionsFailed': '无法读取已安装的软件包,因此不知道该选项列表有哪些扩展。', 'engine.translationPreview.empty': '此语言包为空 —— 至少添加一条译文,即可查看覆盖率报告。', 'engine.translationPreview.category.objects': '对象', 'engine.translationPreview.category.picklists': '选项列表', @@ -2318,41 +1349,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.translationPreview.keyCountOther': '{{count} 个键}', 'engine.celLint.notTheRow': '`{identifier}` 在此处不是记录行:行条件只把记录绑定为 `{canonical}`,别无其他。CEL 作用域词汇仍接受 `{identifier}`,所以这里不会阻止保存,但运行时表达式会以 `Unknown variable: {identifier}` 报错,规则永远不会触发。请改为以 `{canonical}` 为根引用。', - 'engine.objectPreview.renderFailed': '无法渲染表单设计器。请检查“表单”标签页。', - 'engine.datasetPreview.pickObject': '选择一个基础对象', - 'engine.datasetPreview.pickObjectHint': '设置数据集的 `object`,即可用实时数据预览它。', - 'engine.datasetPreview.addMeasure': '添加一个度量', - 'engine.datasetPreview.addMeasureHint': '数据集至少需要一个度量(例如 revenue = sum(amount))才能预览。', - 'engine.datasetPreview.run': '运行预览', - 'engine.datasetPreview.measureOne': '{count} 个度量', - 'engine.datasetPreview.measureOther': '{count} 个度量', - 'engine.datasetPreview.dimensionOne': '{count} 个维度', - 'engine.datasetPreview.dimensionOther': '{count} 个维度', - 'engine.datasetPreview.notInstalled': '未安装分析能力', - 'engine.datasetPreview.notInstalledHint': - '此部署没有分析服务,因此无法运行数据集预览。请安装 @objectstack/service-analytics 并挂载 AnalyticsServicePlugin —— 数据集定义本身没有问题。', - 'engine.datasetPreview.noRows': '没有数据行', - 'engine.datasetPreview.noRowsHint': '数据集在当前范围内没有返回任何数据行。', - 'engine.datasetPreview.chartFailed': '无法为此结果渲染图表 —— 下方表格仍会显示数据。', - 'engine.datasetPreview.ratioAxis': '比率度量({measures})使用右侧坐标轴。', - 'engine.viewPreview.schemaFailed': '该视图的 `type` 可能尚未注册,或缺少必填字段。', - 'engine.viewPreview.noObject': '此视图尚未绑定对象。请在右侧面板中设置绑定的 {object},以获取实时数据和字段选项。', - 'engine.viewPreview.formFailed': '表单视图引用了无法解析的对象或字段。', - 'engine.viewPreview.listFailed': '视图引用了无法解析的对象或字段。', - 'engine.reportPreview.renderFailed': '报表引用了无法解析的数据集/度量,或其配置不完整。', - 'engine.reportPreview.loading': '正在加载报表渲染器…', - 'engine.reportPreview.joinedEmpty': '添加一个区块以预览此联合报表', - 'engine.reportPreview.joinedEmptyHint': '联合报表由绑定数据集的区块堆叠而成。添加一个区块,并在右侧面板中绑定它的数据集 + 度量,即可开始设计。', - 'engine.reportPreview.empty': '绑定一个数据集以预览此报表', - 'engine.reportPreview.emptyHint': '自 9.0 单一形态切换起,报表按维度(rows)分组呈现其数据集的度量(values)。在右侧面板中选择一个数据集即可开始设计。', - 'engine.pagePreview.interfaceFailed': '界面页面引用了一个不可用的来源对象/视图。', - 'engine.pagePreview.schemaFailed': '页面 schema 不完整,或引用了尚未注册的组件。', - 'engine.pagePreview.previewRecord': '预览记录', - 'engine.pagePreview.sampleOne': '{count} 条示例', - 'engine.pagePreview.sampleOther': '{count} 条示例', - 'engine.pagePreview.recordFallback': '(记录)', - 'engine.pagePreview.addComponents': '向页面添加组件即可查看预览。', - 'engine.pagePreview.blockN': '区块 {n}', 'engine.pageBlockCanvas.renderFailed': '“{type}” 无法以当前配置渲染 —— 请检查它的属性。', 'engine.pageBlockCanvas.itemN': '第 {n} 项', 'engine.pageBlockCanvas.body': '主体', @@ -2414,45 +1410,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.pageBlockCanvas.type.elementRepeater': '重复器', 'engine.sourcePageEditor.source': '页面源码', 'engine.sourcePageEditor.renderFailed': '页面源码在渲染时抛出错误 —— 请在“源码”标签页中修复代码。', - 'engine.validationPreview.empty': '为校验规则命名即可查看预览。', - 'engine.validationPreview.active': '已启用', - 'engine.validationPreview.disabled': '已停用', - 'engine.validationPreview.priority': '优先级 {priority}', - 'engine.validationPreview.on': '触发于 {events}', - 'engine.validationPreview.noMessage': '未设置消息', - 'engine.validationPreview.tags': '标签', - 'engine.validationPreview.removed.unique': - '唯一性是唯一索引(INDEX),不是校验规则:请在对象上声明为 `indexes: [{ fields: [...], unique: \'organization\' }]`(每个组织一个持有者),或用 `unique: \'global\'` 表示整个安装唯一(或在字段上设置 `unique: true`)。先 SELECT 再 INSERT 的规则天生存在竞态(TOCTOU);数据库约束则不会。', - 'engine.validationPreview.removed.async': - '远程/异步校验属于表单层的职责 —— 放在服务端写入路径上会带来 SSRF 与延迟风险。请把它留在表单中,或用唯一索引或生命周期钩子来强制其背后的约束。', - 'engine.validationPreview.removed.custom': - '任意校验代码应放在 `beforeInsert` / `beforeUpdate` 生命周期钩子中 —— 那是有类型、受支持的扩展点。', - 'engine.validationPreview.notARule': '不是校验规则', - 'engine.validationPreview.removedType': '{type} 已从 ValidationRuleSchema 中移除 —— 此规则无法保存。', - 'engine.validationPreview.condition': '条件', - 'engine.validationPreview.when': '条件成立时', - 'engine.validationPreview.thenApply': '则应用', - 'engine.validationPreview.noNested': '未设置嵌套规则 —— 此规则不强制任何约束。', - 'engine.validationPreview.otherwiseApply': '否则应用', - 'engine.validationPreview.transitions': '状态流转', - 'engine.validationPreview.transitionsOn': '{field} 的状态流转', - 'engine.validationPreview.noTransitions': '未声明任何状态流转。', - 'engine.validationPreview.deadEnd': '∅(无后续状态)', - 'engine.validationPreview.createdIn': '可在以下状态创建', - 'engine.validationPreview.format': '格式', - 'engine.validationPreview.formatOn': '{field} 的格式', - 'engine.validationPreview.builtIn': '内置:', - 'engine.validationPreview.regex': '正则:', - 'engine.validationPreview.noFormat': '未设置格式或正则表达式。', - 'engine.validationPreview.fieldsInvolved': '涉及字段', - 'engine.validationPreview.none': '无', - 'engine.validationPreview.crossFieldCondition': '跨字段条件', - 'engine.validationPreview.jsonSchemaOn': '{field} 的 JSON Schema', - 'engine.validationPreview.noJsonSchema': '未设置 JSON Schema。', - 'engine.validationPreview.rule': '规则', - 'engine.validationPreview.unknownType': '未知的规则类型“{type}”。仅显示通用字段。', - 'engine.validationPreview.noType': '无类型', - 'engine.validationPreview.noExpression': '未设置表达式。', 'engine.fieldsListEditor.columns': '列', 'engine.fieldsListEditor.empty': '还没有列。请在下方添加字段。', 'engine.fieldsListEditor.colN': '列 {n}', @@ -2553,17 +1510,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.quickfind.noMatches': '没有匹配项。', 'engine.breadcrumb.allTypes': '元数据', - // ── StudioDesignSurface (ADR-0080 WYSIWYG design surface) ────────────── - // Shared chrome - 'engine.studio.cancel': '取消', - 'engine.studio.creating': '创建中…', - 'engine.studio.createItem.savedAs': '保存为', - 'engine.studio.createItem.notDerived': '显示名称里没有可用来生成标识的英文字母,请手动填写英文标识(小写字母、数字和下划线)。', - // `383502b23` — see the English entry. This value must share no leading - // run with `engine.studio.app.create`, which opens the same dialog. - 'engine.studio.createDraft': '存为草稿', - 'engine.studio.saveDraft': '保存草稿', - 'engine.studio.autoSaving': '保存中…', 'engine.draftConflict.title': '此草稿在你打开后已被修改', 'engine.draftConflict.description': '{type}「{name}」的草稿在你打开后已在别处保存(其他人或另一个标签页)。可以重新加载已保存的版本(此处未保存的修改将丢弃),或用你的版本覆盖它。', @@ -2576,223 +1522,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.draftConflict.overwriteConfirm': '覆盖', 'engine.draftConflict.back': '返回', 'engine.draftConflict.notSaved': '未保存:{type}「{name}」在你打开后已在别处保存。', - // Standard create-dialog field labels (shared by object / app / flow / permission). - 'engine.studio.app.nameLabel': '应用名称', - 'engine.studio.app.idLabel': '标识', - 'engine.studio.pkg.manage': '软件包信息与设置', - 'engine.studio.pkg.manageMissing': '软件包 {id} 不在已安装列表中 —— 可能已在别处被删除或卸载。', - 'engine.studio.pkg.manageRefreshFailed': - '软件包 {id} 在该操作后未能刷新 —— 已关闭面板,以免把操作前的记录当作最新呈现。{error}', - 'engine.studio.publish': '发布', - 'engine.studio.loading': '加载中…', - 'engine.studio.loadFailed': '加载失败', - 'engine.studio.refusal.choiceWithoutOptions': - '更改未保存:字段「{field}」至少需要一个选项。请添加选项,或将该字段改为非选项类型。', - 'engine.studio.refusal.relationshipWithoutTarget': - '更改未保存:字段「{field}」未指定要关联的对象。请选择对象,或将该字段改为非关联类型。', - 'engine.studio.refusal.issue': '更改未保存。请检查{where} —— {problem}', - 'engine.studio.refusal.more': '(另有 {count} 处,见详情)', - 'engine.studio.refusal.unlocated': '更改未保存:服务端拒绝了此草稿。详情中说明了需要修改的内容。', - 'engine.studio.refusal.field': '字段「{field}」', - 'engine.studio.refusal.step': '步骤「{step}」', - 'engine.studio.refusal.stepInput': '步骤「{step}」的「{input}」', - 'engine.studio.refusal.navItem': '导航项「{item}」', - 'engine.studio.refusal.navItemInput': '导航项「{item}」的「{input}」', - 'engine.studio.refusal.details': '详情', - 'engine.studio.refusal.show': '定位', - 'engine.studio.held.needsOptions': '字段「{field}」至少需要一个选项', - 'engine.studio.held.needsTarget': '字段「{field}」需要指定要关联的对象', - 'engine.studio.held.needsInput': '步骤「{step}」的「{input}」需要填写', - 'engine.studio.held.needsCondition': '规则「{rule}」需要一个条件', - 'engine.studio.held.needsThenCondition': '规则「{rule}」的「Then」规则需要一个条件', - 'engine.studio.held.needsRuleField': '规则「{rule}」需要选择一个字段', - 'engine.studio.held.needsRuleMessage': '规则「{rule}」需要一条错误消息', - 'engine.studio.held.widgetNeedsInput': '组件「{widget}」的「{input}」需要填写', - 'engine.studio.held.line': '尚未保存:{clause}。更改已保留在此处,补全后会自动保存。', - 'engine.studio.held.publish': '未发布:{clause},该更改尚未保存。请先补全,再发布。', - 'engine.studio.held.inputHint': '必填。填写后才会保存更改。', - 'engine.studio.unpublishedDraft': '未发布草稿', - 'engine.studio.itemDraftPending': '草稿 · {count} 项未发布', - 'engine.studio.itemDraftPendingTitle': '此项有未发布的草稿。本包共有 {count} 项未发布的更改,发布时一起生效。', - 'engine.studio.unpublished': '未发布', - 'engine.studio.new': '新建', - 'engine.studio.edit': '编辑', - 'engine.studio.done': '完成', - 'engine.studio.close': '关闭', - 'engine.studio.deselect': '取消选择', - 'engine.studio.toggleRail': '切换侧栏', - 'engine.studio.landingHome': 'Studio 首页', - 'engine.studio.desktopHint': 'Studio 专为桌面屏幕设计。', - 'engine.studio.aiCopilot': 'AI 副驾', - // objectui#12725 — Studio 右栏:属性或 AI 副驾二选一。 - 'engine.studio.slot.label': '右栏', - 'engine.studio.slot.switchHint': '⌘.(Ctrl+.)切换', - 'engine.studio.slot.collapse': '收起右栏', - 'engine.studio.slot.expand': '展开右栏', - 'engine.studio.slot.backToChat': '回到对话', - 'engine.studio.slot.planPending': '方案待确认', - 'engine.studio.slot.openFullPage': '在完整页面中打开', - 'engine.studio.pillar.data': '数据', - 'engine.studio.pillar.automations': '自动化', - 'engine.studio.pillar.interfaces': '界面', - 'engine.studio.pillar.access': '权限', - // Header · package-level publish + app bridge - 'engine.studio.changes': '变更', - 'engine.studio.publishTitle': '一次性确认并发布全部待发布草稿(整包 · 一次原子发布)', - 'engine.studio.publishNoneTitle': '没有待发布的草稿', - 'engine.studio.publishedAll': '已发布本软件包的全部草稿(一次原子发布)', - // objectui#11807 — the publish toast names how many items went live. - 'engine.studio.publishedAllCount': '已发布本软件包的 {count} 项(一次原子发布)', - 'engine.studio.publishedAllCountOne': '已发布本软件包的 {count} 项(一次原子发布)', - // objectui#12929 — the batch door's backfill receipt, one clause per field - // the review sheet named: the rows the server wrote, and why a field stopped. - 'engine.studio.backfillWritten': '已为 {field} 补默认值:{count} 条记录', - 'engine.studio.backfillStopped': '{field} 的默认值补写中途停止(停止前已写入 {count} 条):{error}', - 'engine.studio.app.open': '打开应用', - 'engine.studio.app.openTitle': '打开应用「{label}」(发布后的前端界面)', - 'engine.studio.app.create': '创建应用', - 'engine.studio.app.noneTitle': '这个软件包还没有应用(前端界面)— 创建一个', - 'engine.studio.app.willOpenAfterPublish': '发布后这里会变成「打开应用」', - 'engine.studio.app.pending': '应用「{label}」待发布', - 'engine.studio.app.namePlaceholder': '应用名称(如:订单中心)', - 'engine.studio.app.idPlaceholder': '标识符(如:orders_app)', - 'engine.studio.app.savedDraft': '应用「{label}」已存为草稿 — 发布后即可打开', - // Package switcher - 'engine.studio.pkg.switchTitle': '切换 / 新建软件包', - 'engine.studio.pkg.readonly': '只读', - 'engine.studio.pkg.readonlyHint': '只读软件包 — 请切换或新建可写软件包后再编辑。', - 'engine.studio.pkg.readonlyLink': '只读 · 用覆盖定制', - 'engine.studio.app.scaffoldNav': '将本包的对象添加为菜单项', - 'engine.studio.landing.title': '应用构建', - 'engine.studio.landing.description': '在一个可写软件包里设计对象、表单、自动化与界面;编辑存为草稿,整包一次发布。源码与已安装的软件包为只读:可在此浏览,并用环境覆盖进行定制。', - 'engine.studio.landing.mineHeading': '我的软件包(可写)', - 'engine.studio.landing.noneWritable': '还没有可写软件包 — 新建一个开始。', - 'engine.studio.landing.dup': '复制', - 'engine.studio.landing.dupTitle': '复制这个软件包为新的可写副本(类似「复制基座」)', - 'engine.studio.landing.dupDefaultName': '{name} 副本', - 'engine.studio.landing.dupNamePlaceholder': '副本名称', - 'engine.studio.landing.dupIdPlaceholder': '副本包 ID', - 'engine.studio.landing.dupGo': '复制并进入构建器', - 'engine.studio.landing.dupCreated': '已复制为可写软件包「{name}」', - 'engine.studio.landing.installedHeading': '已安装(只读 · 可浏览)', - // objectui#11808 — the routes a read-only package does have. - 'engine.studio.landing.readonlyHint': '源码与已安装的软件包为只读,不能在此编辑或复制。对于接受环境覆盖的元数据类型,可用环境覆盖(overlay)进行定制。', - 'engine.studio.landing.overlay': '在设置中打开', - 'engine.studio.landing.overlayTitle': '打开该软件包的元数据:每种类型都会标明是否接受环境覆盖', - 'engine.studio.landing.marketplace': '或从应用市场安装一个模板应用', - // objectui#11553 — the package-less scope: the organization's own flows. - 'engine.studio.landing.orgHeading': '不属于软件包', - 'engine.studio.org.name': '组织流程', - 'engine.studio.org.hint': '不属于任何软件包的流程', - 'engine.studio.org.description': '本组织自有、不属于任何软件包的流程,例如软件包流程的克隆副本。可直接编辑。', - 'engine.studio.org.none': '还没有不属于软件包的流程。在 设置 › 打包自动化 中克隆一个软件包流程,即可在这里编辑副本。', - 'engine.studio.org.openSetup': '在设置中打开', - 'engine.studio.publishedAllFlows': '已发布 {count} 个不属于软件包的流程草稿', - 'engine.studio.publishedAllFlowsOne': '已发布 {count} 个不属于软件包的流程草稿', - 'engine.studio.designer.search': '搜索…', - 'engine.studio.designer.select': '请选择…', - 'engine.studio.designer.pickDate': '选择日期…', - 'engine.studio.designer.fieldAria': '{label} — 点选改属性,拖动排序', - 'engine.studio.designer.fieldAriaReadOnly': '{label} — 点选查看属性', - 'engine.studio.designer.fieldRole': '可拖动字段', - 'engine.studio.designer.groupUp': '上移分组', - 'engine.studio.designer.groupDown': '下移分组', - 'engine.studio.designer.groupDelete': '删除分组', - 'engine.studio.designer.groupDeleteTitle': '删除分组(字段归为未分组)', - 'engine.studio.designer.dropHere': '拖字段到这里', - 'engine.studio.designer.ungrouped': '未分组', - 'engine.studio.designer.newGroup': '新分组', - 'engine.studio.designer.fieldTitleReadOnly': '点选字段查看属性', - 'engine.studio.designer.toolbarHint': '拖动字段排布 · 点选字段编辑属性', - 'engine.studio.designer.addGroup': '添加分组', - 'engine.studio.formDnd.instructions': - '按空格键或回车键拿起字段。拖动时用方向键移动,再按空格键或回车键放到新位置,按 Esc 键取消。', - 'engine.studio.formDnd.start': '已拿起 {field},当前在 {group},第 {position} 个,共 {total} 个。', - 'engine.studio.formDnd.over': '{field} 正移到 {group},第 {position} 个,共 {total} 个。', - 'engine.studio.formDnd.overNone': '{field} 不在任何分组上。', - 'engine.studio.formDnd.end': '{field} 已移到 {group},第 {position} 个,共 {total} 个。', - 'engine.studio.formDnd.endNone': '{field} 没有放到分组里,已回到 {group},第 {position} 个,共 {total} 个。', - 'engine.studio.formDnd.cancel': '已取消拖动。{field} 已回到 {group},第 {position} 个,共 {total} 个。', - 'engine.studio.rules.title': '验证规则', - 'engine.studio.rules.none': '还没有验证规则。', - 'engine.studio.rules.newButton': '新建验证规则', - 'engine.studio.rules.noMessage': '(无消息)', - 'engine.studio.rules.notSaved': '未保存 —— 需要条件', - 'engine.studio.rules.notSavedField': '未保存 —— 需要字段', - 'engine.studio.rules.notSavedMessage': '未保存 —— 需要消息', - 'engine.studio.rules.structured': '「{type}」类型的规则带有结构化配置(状态机转移表 / 格式约束等),暂不支持在此编辑 —— 请在代码包中维护。消息:{message}', - 'engine.studio.rules.none2': '(无)', - 'engine.studio.rules.deleteRule': '删除规则', - 'engine.studio.rules.nameLabel': '规则名(snake_case,唯一)', - 'engine.studio.rules.messageLabel': '错误消息(校验失败时展示给用户)', - 'engine.studio.rules.messagePlaceholder': '例如:完成日期在状态为已完成时必填', - 'engine.studio.rules.celPre': '失败条件(CEL)—— 条件为 ', - 'engine.studio.rules.celTrue': '真', - 'engine.studio.rules.celMid': ' 时,拒绝保存并显示上面的消息。', - 'engine.studio.rules.whenPre': '前提条件(CEL)—— 条件为 ', - 'engine.studio.rules.whenMid': ' 时,应用下面的「Then」规则;否则应用「Otherwise」规则(如已设置)。', - 'engine.studio.rules.severity': '严重度', - 'engine.studio.rules.severityError': 'error(拒绝保存)', - 'engine.studio.rules.enabled': '启用', - 'engine.studio.rules.delete': '删除', - 'engine.studio.rules.type': '类型', - 'engine.studio.rules.newType': '新建验证类型', - 'engine.studio.rules.typeScript': 'Script —— CEL 失败条件', - 'engine.studio.rules.typeCrossField': 'Cross-field —— 跨字段 CEL', - 'engine.studio.rules.typeStateMachine': 'State machine —— 允许的状态流转', - 'engine.studio.rules.typeFormat': 'Format —— 正则 / 内置格式', - 'engine.studio.rules.typeJsonSchema': 'JSON schema —— 校验 JSON 字段', - 'engine.studio.rules.typeConditional': 'Conditional —— 条件成立时应用规则', - 'engine.studio.rules.label': '标签(可选)', - 'engine.studio.rules.field': '字段', - 'engine.studio.rules.statusField': '状态字段', - 'engine.studio.rules.fields': '参与字段', - 'engine.studio.rules.noFields': '该对象暂无字段。', - 'engine.studio.rules.pickField': '—— 选择字段 ——', - 'engine.studio.rules.format': '内置格式', - 'engine.studio.rules.formatNone': '—— 无 ——', - 'engine.studio.rules.regex': '正则表达式', - 'engine.studio.rules.transitions': '允许的状态流转(从 → 下一状态)', - 'engine.studio.rules.transitionFrom': '起始状态', - 'engine.studio.rules.transitionTo': '下一状态(逗号分隔)', - 'engine.studio.rules.addTransition': '新增流转', - 'engine.studio.rules.jsonSchema': 'JSON Schema', - 'engine.studio.rules.invalidJson': 'JSON 格式无效', - 'engine.studio.rules.then': 'Then —— 条件成立时应用的规则(JSON)', - 'engine.studio.rules.otherwise': 'Otherwise —— 条件不成立时应用的规则(JSON,可选)', - 'engine.studio.rules.events': '触发时机', - 'engine.studio.rules.event.insert': '新建', - 'engine.studio.rules.event.update': '更新', - 'engine.studio.rules.priority': '优先级', - 'engine.studio.rules.presets': '常用规则', - 'engine.studio.rules.advanced': '高级', - 'engine.studio.rules.preset.endAfterStart': '结束日期不早于开始日期', - 'engine.studio.rules.preset.endAfterStartNeeds': '需要该对象有两个日期字段。', - 'engine.studio.rules.preset.endAfterStartMessage': '{end}不能早于{start}。', - 'engine.studio.rules.preset.notNegative': '数值不能为负', - 'engine.studio.rules.preset.notNegativeNeeds': '需要该对象有一个数值字段。', - 'engine.studio.rules.preset.notNegativeMessage': '{field}不能为负数。', - 'engine.studio.rules.preset.rejectWhen': '满足条件时拒绝保存…', - 'engine.studio.rules.preset.rejectWhenHint': '自行搭建条件,无需写代码。', - 'engine.studio.rules.preset.usesOne': '使用「{field}」。', - 'engine.studio.rules.preset.usesTwo': '使用「{start}」和「{end}」。', - 'engine.studio.pkg.writable': '可写', - 'engine.studio.pkg.heading': '软件包(应用)', - 'engine.studio.pkg.none': '暂无应用软件包', - 'engine.studio.pkg.createWritable': '创建可写软件包', - 'engine.studio.pkg.new': '新建软件包(可写基座)', - 'engine.studio.pkg.created': '软件包 {name} 已创建(可写)', - // Nav item inspector (Interfaces pillar) - 'engine.studio.nav.selectItem': '在左侧选择一个菜单项。', - 'engine.studio.nav.label': '标签', - 'engine.studio.nav.linkObject': '链接到对象', - 'engine.studio.nav.chooseObject': '— 选择对象 —', - 'engine.studio.nav.boundHint': '这个菜单项会打开该对象的记录列表。', - 'engine.studio.nav.unboundHint': '选择一个对象,菜单项将打开它的记录列表。', - 'engine.studio.nav.noObjects': '这个软件包还没有对象 — 请先在「数据」中创建一个。', - 'engine.studio.nav.eyebrow': '导航项 · {kind}', - 'engine.studio.nav.icon': '图标', - 'engine.studio.nav.visibleWhen': '可见条件', 'engine.studio.inspector.props': '属性', 'engine.studio.inspector.draftFooter': '编辑先保存为草稿,发布包后生效。', 'engine.studio.inspector.collapse': '收起属性', @@ -2830,117 +1559,9 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.inspector.listView.kanbanCard.mode.none': '不打开', 'engine.studio.inspector.listView.kanbanCard.groupBy': '分组字段', 'engine.studio.inspector.listView.kanbanCard.pickField': '选择字段', - 'engine.studio.draftMark.chip': '草稿', - 'engine.studio.draftMark.sourceLabel': '来源', - 'engine.studio.draftMark.source': '由 AI 副驾在 {when}作为草稿加入', - 'engine.studio.draftMark.sourceJustNow': '由 AI 副驾刚刚作为草稿加入', - 'engine.studio.draftMark.sourceItems': '{items} {count} 项由 AI 副驾在 {when}作为草稿加入', - 'engine.studio.draftMark.sourceItemsJustNow': '{items} {count} 项由 AI 副驾刚刚作为草稿加入', - 'engine.studio.draftMark.item': '「{label}」', - 'engine.studio.draftMark.minutesAgo': '{n} 分钟前', - 'engine.studio.draftMark.hoursAgo': '{n} 小时前', - 'engine.studio.draftMark.daysAgo': '{n} 天前', - // objectui#11823 (step 2) — the Interfaces pillar's New dashboard / New report. - 'engine.studio.interfaces.create.menuTitle': '在本包中新建仪表盘、报表或页面', - 'engine.studio.interfaces.create.dashboard': '新建仪表盘', - 'engine.studio.interfaces.create.report': '新建报表', - 'engine.studio.interfaces.create.description': '存为本包的草稿,加入 {app} 的导航,并在画布上打开。', - 'engine.studio.interfaces.create.dashboardNameLabel': '仪表盘名称', - 'engine.studio.interfaces.create.reportNameLabel': '报表名称', - 'engine.studio.interfaces.create.dashboardNamePlaceholder': '仪表盘名称(如:销售概览)', - 'engine.studio.interfaces.create.reportNamePlaceholder': '报表名称(如:各区域收入)', - 'engine.studio.interfaces.create.idLabel': '标识', - 'engine.studio.interfaces.create.dashboardIdPlaceholder': '标识符(如:sales_overview)', - 'engine.studio.interfaces.create.reportIdPlaceholder': '标识符(如:revenue_by_region)', - 'engine.studio.interfaces.create.dashboardTaken': '已有标识为「{name}」的仪表盘,请换一个标识。', - 'engine.studio.interfaces.create.reportTaken': '已有标识为「{name}」的报表,请换一个标识。', - 'engine.studio.interfaces.create.dataset': '数据集', - 'engine.studio.interfaces.create.datasetPlaceholder': '选择数据集…', - 'engine.studio.interfaces.create.measure': '度量', - 'engine.studio.interfaces.create.measurePlaceholder': '选择度量…', - 'engine.studio.interfaces.create.reportHint': '报表展示一个数据集的度量。创建后可在属性面板中添加更多度量与分组。', - 'engine.studio.interfaces.create.noDatasets': '还没有可用于报表的数据集。报表展示的是数据集的度量,请先创建数据集。', - 'engine.studio.interfaces.create.datasetsFailed': '无法加载数据集:{error}', - 'engine.studio.interfaces.create.noMeasures': '这个数据集没有声明度量,报表无法展示它。', - 'engine.studio.interfaces.create.needsBinding': '请选择这个报表读取的数据集,以及其中的一个度量。', - 'engine.studio.interfaces.create.page': '新建页面', - 'engine.studio.interfaces.create.pageNameLabel': '页面名称', - 'engine.studio.interfaces.create.pageNamePlaceholder': '页面名称(如:团队手册)', - 'engine.studio.interfaces.create.pageIdPlaceholder': '标识符(如:team_handbook)', - 'engine.studio.interfaces.create.pageTaken': '已有标识为「{name}」的页面,请换一个标识。', - 'engine.studio.interfaces.create.pageKind': '编写方式', - 'engine.studio.interfaces.create.pageKindHtml': 'HTML', - 'engine.studio.interfaces.create.pageKindReact': 'React', - 'engine.studio.interfaces.create.pageKindHtmlHint': '由平台组件与 HTML 标签组成,只解析、不执行。页面打开时,源码与实时预览并排显示。', - 'engine.studio.interfaces.create.pageKindReactHint': '真正的 React 代码,在页面渲染时运行,可使用状态、副作用与事件处理。页面打开时,源码与实时预览并排显示。', - 'engine.studio.interfaces.create.pageStarter': '新页面。请在源码编辑器中编写它的内容。', 'engine.studio.inspector.designersMissing': '本次会话没有注册任何元数据设计器,这里没有可编辑的内容。', 'engine.studio.inspector.noPageSchema': '页面设置不可用——无法加载页面 schema。', - // Actions view - 'engine.studio.actions.none': '该对象没有操作。', - 'engine.studio.actions.newButton': '新建操作', - 'engine.studio.actions.editorMissing': '本次会话没有注册操作编辑器,因此无法在这里编辑该操作的属性。', - 'engine.studio.actions.newLabel': '新操作', - 'engine.studio.actions.delete': '删除', - 'engine.studio.actions.presets': '常用操作', - 'engine.studio.actions.notSaved': '未保存 —— 需要「{input}」', - 'engine.studio.actions.heldNeeds': '操作「{action}」需要「{input}」', - 'engine.studio.actions.preset.change': '修改下拉选择字段', - 'engine.studio.actions.preset.changeHint': '询问新的「{field}」,然后保存。', - 'engine.studio.actions.preset.changeNeeds': '需要该对象有一个下拉选择字段。', - 'engine.studio.actions.preset.changeLabel': '修改{field}', - 'engine.studio.actions.preset.runFlow': '运行流程', - 'engine.studio.actions.preset.openUrl': '打开网页', - 'engine.studio.actions.preset.openPage': '在对话框中打开页面', - // API view - 'engine.studio.api.subtitle': - '该对象自动生成的 REST 端点,依据当前 schema 派生。可复制为 cURL,带上你的 API 令牌调用。', - 'engine.studio.api.list': '列出记录', - 'engine.studio.api.create': '创建记录', - 'engine.studio.api.get': '按 id 获取单条', - 'engine.studio.api.update': '更新记录', - 'engine.studio.api.delete': '删除记录', - 'engine.studio.api.copyCurl': '复制为 cURL', - 'engine.studio.api.copied': '已复制', - 'engine.studio.api.body': '示例请求体', - 'engine.studio.api.bodyOmitted': '必填字段排在最前。未列出的可选字段:{fields}', - // Hooks view - 'engine.studio.hooks.renameHeld': '尚未保存:钩子会保留创建时的名称。把名称改回 {name} 即可保存更改。', - 'engine.studio.hooks.none': '没有钩子作用于该对象。', - 'engine.studio.hooks.newButton': '新建钩子', - 'engine.studio.hooks.async': '异步', - 'engine.studio.hooks.pick': '选择一个钩子进行编辑。', - 'engine.studio.hooks.newLabel': '新钩子', - 'engine.studio.designer.group.kind': '分组', - 'engine.studio.designer.group.settings': '分组设置', - 'engine.studio.designer.group.nameLabel': '分组名称', - 'engine.studio.designer.group.missing': '该分组已不存在。', - 'engine.studio.designer.group.collapseLabel': '折叠方式', - 'engine.studio.designer.group.collapseNone': '不可折叠', - 'engine.studio.designer.group.collapseExpanded': '可折叠 · 默认展开', - 'engine.studio.designer.group.collapseCollapsed': '可折叠 · 默认折叠', - 'engine.studio.designer.group.collapseHint': '控制该分组在真实表单(预览)中的折叠方式。', - // objectui#11788 — 新建自动化对话框里不选触发方式时的选项。 - 'engine.studio.newAutoTrigger.later': '稍后在开始节点上选择', - // ── 记录共享基线(OWD)总览 (objectui#2505) ────────────────────────── - 'engine.studio.owd.railLabel': '记录共享(OWD)', - 'engine.studio.owd.title': '记录共享基线(OWD)', - 'engine.studio.owd.subtitle': '对象 × 组织级默认', - 'engine.studio.owd.description': - '本包每个对象的组织级默认(sharingModel)—— 一屏审计基线并批量修改。改动以“逐对象草稿”保存,随包发布上线,与对象“设置”页完全一致。', - 'engine.studio.owd.colObject': '对象', - 'engine.studio.owd.colInternal': '共享模型(sharingModel)', - 'engine.studio.owd.none': '本包未声明任何对象。', - 'engine.studio.owd.loadFailed': '加载本包对象失败。', - 'engine.studio.owd.save': '保存 {count} 处改动', - 'engine.studio.owd.saveNone': '无改动', - 'engine.studio.owd.saved': '已保存 {count} 个对象草稿。', - 'engine.studio.owd.unsaved': '未保存', - 'engine.studio.owd.byParent': '受父级控制 → {master}', - 'engine.studio.owd.byParentNoMaster': '受父级控制 —— 没有主记录:该对象需要恰好一个必填的主从字段', - 'engine.studio.owd.masterTip': - '受父级控制 —— 记录可见性继承自主记录。', // ── AppNavCanvas (nav-tree editor, used by Studio + AppPreview) ───────── 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 231bffeef0e..a4e14dd35d8 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,12 +1,12 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * objectstack#25922 slices S2 to S4 — the designer rows that moved into + * objectstack#25922 slices S2 to S5 — the designer rows that moved into * `@object-ui/i18n`'s `studio` namespace (every `engine.inspector.*` row; - * `engine.packages.*`, `engine.form.*`, `engine.edit.*`; five `engine.studio.*` - * pillars, the data source pages and the field types), and app-shell's `t()` / - * `tOptional()` still answer every one of them the way they did when the rows - * sat in its own table. + * `engine.packages.*`, `engine.form.*`, `engine.edit.*`; `engine.studio.*` but + * its inspector; the data source pages, the field types and the metadata + * preview panes), 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 @@ -54,6 +54,30 @@ const FAMILY_FLOOR: Readonly> = Object.freeze({ 'engine.fieldType.': 45, 'engine.fieldTypeDesc.': 45, 'engine.fieldCategory.': 9, + // S5 — the rest of engine.studio (its inspector aside) and the preview panes + 'engine.studio.': 600, + 'engine.studio.rules.': 55, + 'engine.studio.interfaces.': 30, + 'engine.studio.designer.': 20, + 'engine.actionPreview.': 50, + 'engine.permissionPreview.': 35, + 'engine.validationPreview.': 30, + 'engine.jobPreview.': 18, + 'engine.agentPreview.': 15, + 'engine.skillPreview.': 15, + 'engine.datasetPreview.': 12, + 'engine.toolPreview.': 12, + 'engine.picklistPreview.': 12, + 'engine.dashboardPreview.': 9, + 'engine.appPreview.': 9, + 'engine.emailTemplatePreview.': 9, + 'engine.pagePreview.': 8, + 'engine.screenPreview.': 7, + 'engine.positionPreview.': 6, + 'engine.reportPreview.': 6, + 'engine.viewPreview.': 4, + 'engine.objectPreview.': 1, + 'engine.previewShell.': 1, }); /** diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.areas-11027.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.areas-11027.test.tsx index 20273e0537b..fb87a10f48f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.areas-11027.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.areas-11027.test.tsx @@ -23,6 +23,12 @@ import { render, screen, cleanup } from '@testing-library/react'; import { t } from '../i18n'; import { AppPreview } from './AppPreview'; +// 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); const NAV = [{ id: 'accounts', type: 'object', label: 'Accounts', objectName: 'account' }]; diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.i18nTitle-10847.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.i18nTitle-10847.test.tsx index 39991770724..32ef840b35f 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.i18nTitle-10847.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.i18nTitle-10847.test.tsx @@ -38,6 +38,12 @@ vi.mock('@object-ui/react', async (importOriginal) => { import { DashboardPreview } from './DashboardPreview'; import type { MetadataSelection } from '../preview-registry'; +// 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); type Patch = Record; diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/ScreenPreview.fieldProjection-11190.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/ScreenPreview.fieldProjection-11190.test.tsx index 3099a29fe09..8e7eb063197 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/ScreenPreview.fieldProjection-11190.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/ScreenPreview.fieldProjection-11190.test.tsx @@ -32,6 +32,12 @@ import { ScreenPreview } from './ScreenPreview'; import { buildScreenSpec } from './screen-spec'; import { ScreenView, initialScreenValues, type ScreenFieldSpec, type ScreenSpec } from '../../ScreenView'; +// 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/automationPreviews.i18n-10862.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/automationPreviews.i18n-10862.test.tsx index e39aafdd987..12191e11dfb 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/automationPreviews.i18n-10862.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/automationPreviews.i18n-10862.test.tsx @@ -40,6 +40,12 @@ import { SkillPreview } from './SkillPreview'; import { ToolPreview } from './ToolPreview'; import { JobPreview } from './JobPreview'; +// 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(); vi.unstubAllGlobals(); diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10848.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10848.test.tsx index 797aeaa0bc1..8b8dc7435e0 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10848.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10848.test.tsx @@ -99,6 +99,12 @@ import { FlowSimulatorPanel } from './FlowSimulatorPanel'; import { FlowNodeInspector } from '../inspectors/FlowNodeInspector'; 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(); state.canvas = 'draw'; diff --git a/ui/packages/app-shell/src/views/metadata-admin/studio-locale.i18n.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/studio-locale.i18n.test.tsx index 1cea4953571..6b2b87dc724 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/studio-locale.i18n.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/studio-locale.i18n.test.tsx @@ -23,6 +23,12 @@ import { createI18n, I18nProvider } from '@object-ui/i18n'; import { t } from './i18n'; import { AppNavCanvas } from './previews/AppNavCanvas'; +// 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); // Representative keys spanning shared chrome, every pillar, and AppNavCanvas. diff --git a/ui/packages/app-shell/src/views/studio-design/AddFieldPopover.studioNamespace-25922.test.tsx b/ui/packages/app-shell/src/views/studio-design/AddFieldPopover.studioNamespace-25922.test.tsx index 3ed2ac3a085..9a066d3d97a 100644 --- a/ui/packages/app-shell/src/views/studio-design/AddFieldPopover.studioNamespace-25922.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/AddFieldPopover.studioNamespace-25922.test.tsx @@ -4,21 +4,24 @@ * objectstack#25922, (M) slice 1 — a Studio designer screen reads the strings * that moved into `@object-ui/i18n`'s `studio` namespace. * - * The Data pillar's "Add field" popover mixes both homes on one screen: its - * field labels (`designer.field.label` / `.apiName` / `.type`) moved into the - * namespace, its Cancel button (`engine.studio.cancel`) is still in the - * app-shell table (its title and Create button followed in slice S4). It takes its locale from `useMetadataLocale()`, the hook - * every designer component uses, so this is the path a real screen takes: + * The Data pillar's "Add field" popover reads the namespace through both + * doors: its field labels (`designer.field.label` / `.apiName` / `.type`, + * slice 1) straight from it, its chrome (`engine.studio.*`, slices S4 and S5) + * through app-shell's `t()`, which answers from the namespace once the + * designer table no longer holds the row. It takes its locale from + * `useMetadataLocale()`, the hook every designer component uses, so this is + * the path a real screen takes: * * - zh: the namespace's `zh` catalogue is NOT resident when the screen first * renders (this file runs in an isolated `dom` project and preloads * nothing). The first paint reads the moved labels in English — the * fallback, never the raw key — and the same mount re-renders in Chinese - * once the catalogue lands. The table's keys read Chinese throughout. - * - de: a language neither home ships renders English from both. + * once the catalogue lands, chrome included. + * - de: a language the namespace does not ship renders English through both + * doors. * - * Expectations are read from the namespace and the table rather than typed - * out, so no case restates a translation. + * Expectations are read from the namespace and `t()` rather than typed out, so + * no case restates a translation. */ import '@testing-library/jest-dom/vitest'; @@ -39,8 +42,8 @@ afterEach(cleanup); /** Keys this screen renders from the namespace. */ const MOVED = ['designer.field.label', 'designer.field.type'] as const; -/** A key this screen renders from the app-shell table. */ -const TABLE = 'engine.studio.cancel'; +/** A chrome key this screen reads through app-shell's `t()`. */ +const CHROME = 'engine.studio.cancel'; function Screen({ lang }: { lang: string }) { return ( @@ -58,9 +61,8 @@ const en = (key: string) => studioString(key, 'en')!; describe('AddFieldPopover — strings from the studio namespace (objectstack#25922)', () => { it('zh: English while the catalogue is in flight, then Chinese on the same mount', async () => { - // The premise: these keys are the namespace's, and the title is the table's. - for (const key of MOVED) expect(en(key), key).toBeDefined(); - expect(studioString(TABLE, 'en')).toBeUndefined(); + // The premise: every one of these keys is the namespace's. + for (const key of [...MOVED, CHROME]) expect(en(key), key).toBeDefined(); expect(isStudioCatalogueLoaded('zh-CN')).toBe(false); render(); @@ -77,14 +79,14 @@ describe('AddFieldPopover — strings from the studio namespace (objectstack#259 for (const key of MOVED) expect(zh[key], key).not.toBe(en(key)); expect(await screen.findByText(zh['designer.field.label'])).toBeInTheDocument(); for (const key of MOVED) expect(popover()).toHaveTextContent(zh[key]); - expect(popover()).toHaveTextContent(t(TABLE, 'zh-CN')); - expect(t(TABLE, 'zh-CN')).not.toBe(t(TABLE, 'en-US')); + expect(popover()).toHaveTextContent(t(CHROME, 'zh-CN')); + expect(t(CHROME, 'zh-CN')).not.toBe(t(CHROME, 'en-US')); }); - it('de: a third locale renders English from both homes', () => { + it('de: a third locale renders English through both doors', () => { render(); for (const key of MOVED) expect(popover()).toHaveTextContent(en(key)); - expect(popover()).toHaveTextContent(t(TABLE, 'en-US')); + expect(popover()).toHaveTextContent(t(CHROME, 'en-US')); for (const key of MOVED) expect(t(key, 'de')).toBe(en(key)); }); }); diff --git a/ui/packages/app-shell/src/views/studio-design/CreateItemDialog.notDerived-13389.test.tsx b/ui/packages/app-shell/src/views/studio-design/CreateItemDialog.notDerived-13389.test.tsx index fce012f3aaa..1764c63127d 100644 --- a/ui/packages/app-shell/src/views/studio-design/CreateItemDialog.notDerived-13389.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/CreateItemDialog.notDerived-13389.test.tsx @@ -17,6 +17,12 @@ import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { CreateItemDialog } from './CreateItemDialog'; import { t, type SupportedLocale } from '../metadata-admin/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); function renderDialog(locale: SupportedLocale = 'en-US') { diff --git a/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.dndAnnouncements-11802.test.tsx b/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.dndAnnouncements-11802.test.tsx index 8be5b5f12d1..7970876ad1d 100644 --- a/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.dndAnnouncements-11802.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.dndAnnouncements-11802.test.tsx @@ -45,6 +45,12 @@ vi.mock('@dnd-kit/core', async (importOriginal) => { import { ObjectFormDesigner } from './ObjectFormDesigner'; +// 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'); + type Field = { name: string; type: string; label: string; group?: string }; const DRAFT = { diff --git a/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.roleDescription-11872.test.tsx b/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.roleDescription-11872.test.tsx index 61b51bf5b71..f8cae704a31 100644 --- a/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.roleDescription-11872.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.roleDescription-11872.test.tsx @@ -26,6 +26,12 @@ import { I18nProvider, createI18n } from '@object-ui/i18n'; import { ObjectFormDesigner } from './ObjectFormDesigner'; import { t, tFormat } from '../metadata-admin/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'); + const ROLE_KEY = 'engine.studio.designer.fieldRole'; /** dnd-kit's own defaults: `useSortable`'s, and `useDraggable`'s under it. */ const LIBRARY_DEFAULTS = ['sortable', 'draggable']; diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacePolish.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacePolish.test.tsx index 23f758d1026..888fe74af4d 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacePolish.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacePolish.test.tsx @@ -55,6 +55,12 @@ vi.mock('./StudioAiCopilot', () => ({ StudioChatDock: () => null })); import { StudioDesignSurface } from './StudioDesignSurface'; +// 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'); + window.matchMedia = ((query: string) => ({ matches: false, media: query, diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDraftMark.test.ts b/ui/packages/app-shell/src/views/studio-design/StudioDraftMark.test.ts index a77e02c9608..01ae27fff93 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDraftMark.test.ts +++ b/ui/packages/app-shell/src/views/studio-design/StudioDraftMark.test.ts @@ -10,6 +10,14 @@ import { describe, expect, it } from 'vitest'; import type { DraftChange } from '@object-ui/data-objectstack'; import { addedArrayElements, copilotDraftMarkOf, draftSourceText } from './StudioDraftMark'; +// The `engine.studio.*` 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: 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'); + const NOW = Date.parse('2026-10-19T10:00:00.000Z'); const ago = (minutes: number) => new Date(NOW - minutes * 60_000).toISOString(); const entry = (path: string[], by: DraftChange['by'], minutes = 2): DraftChange => ({ path, by, actor: 'usr_1', at: ago(minutes) }); diff --git a/ui/packages/app-shell/src/views/studio-design/metadataError.authorRefusal-11785.test.ts b/ui/packages/app-shell/src/views/studio-design/metadataError.authorRefusal-11785.test.ts index 1ec9b0e61cd..8c25ebc61fc 100644 --- a/ui/packages/app-shell/src/views/studio-design/metadataError.authorRefusal-11785.test.ts +++ b/ui/packages/app-shell/src/views/studio-design/metadataError.authorRefusal-11785.test.ts @@ -25,6 +25,14 @@ import { } from './metadataError'; import { t, tFormat } from '../metadata-admin/i18n'; +// The `engine.studio.*` 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: 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'); + /** Text an author cannot act on: a class or package name, a tracker id, a raw path, code quotes. */ const DEVELOPER_TEXT = /MetadataClient|@objectstack|objectstack#|objectui#|`|fields\.|nodes\.\d|navigation\.\d/; diff --git a/ui/packages/i18n/src/namespaces/studio/en.ts b/ui/packages/i18n/src/namespaces/studio/en.ts index 6225fdbb499..a7e950fa8e6 100644 --- a/ui/packages/i18n/src/namespaces/studio/en.ts +++ b/ui/packages/i18n/src/namespaces/studio/en.ts @@ -2445,6 +2445,753 @@ const STUDIO_EN: Record = { 'engine.studio.access.discardConfirm': 'Discard the pending draft of “{item}”? The published set stays as it is; only its unpublished changes are removed.', 'engine.studio.access.discardConfirmUnpublished': 'Discard “{item}”? It has never been published, so discarding its draft removes the set.', 'engine.studio.access.discardFailed': 'Could not discard the draft', + // The rest of engine.studio.* (all but engine.studio.inspector) and the + // metadata preview panes (engine.*Preview, engine.previewShell; the doc, book, + // flow, page-block and translation previews excepted) (#25922 slice S5). + // objectui#10835 — the dashboard designer's preview canvas (`DashboardPreview`): + // the empty-canvas message, the error-boundary hint, the loading text and the + // selected-widget strip. Display only: a widget's stored `title` is author + // data and shows as written. + 'engine.dashboardPreview.empty': 'Add at least one widget to see a preview.', + 'engine.dashboardPreview.malformed': "A widget references an object or field that doesn't resolve.", + 'engine.dashboardPreview.loading': 'Loading dashboard renderer…', + 'engine.dashboardPreview.selected': 'Selected', + 'engine.dashboardPreview.clickToRename': 'Click to rename', + 'engine.dashboardPreview.untitled': 'untitled', + 'engine.dashboardPreview.saveTitle': 'Save title', + 'engine.dashboardPreview.renameWidget': 'Rename widget', + 'engine.dashboardPreview.clearSelection': 'Clear selection', + // objectui#10848 — the preview chrome still English under zh-CN after + // objectui#10835: `PreviewErrorBoundary`'s heading (every designer preview), + // and `ScreenPreview`'s own words (the flow node inspector and the Debug + // run's paused screen). Display only: a screen's title, description and + // field labels are author data and show as written. + 'engine.previewShell.renderFailed': 'Preview failed to render', + 'engine.screenPreview.header': 'Preview', + 'engine.screenPreview.empty': 'Add a title, description, fields, or an object form to preview this screen.', + 'engine.screenPreview.submit': 'Submit', + 'engine.screenPreview.noDataSource': 'Connect to a backend to preview this object form.', + 'engine.screenPreview.hiddenOne': '{count} field hidden by its “visible when” condition.', + 'engine.screenPreview.hiddenOther': '{count} fields hidden by their “visible when” conditions.', + 'engine.screenPreview.defaultTemplate': 'Default template for “{field}”, shown as written — the run fills it in:', + // objectui#10862 (slice 1: the flow / automation previews) — the designer's + // own words in `ActionPreview`, `AgentPreview`, `SkillPreview`, + // `ToolPreview` and `JobPreview`, read in the `locale` each preview is + // handed. Each en row is the literal it replaced. Author data (labels, + // names, instructions, descriptions), identifiers (enum tokens, object and + // capability names, JSON-Schema types, compact durations such as `5m`) and + // producer text (a response body, a fetch error) show as written. + 'engine.actionPreview.empty': 'Set name and label to see the action preview.', + 'engine.actionPreview.pill.type': 'type: {type}', + 'engine.actionPreview.pill.object': 'object: {object}', + 'engine.actionPreview.pill.variant': 'variant: {variant}', + 'engine.actionPreview.pill.refreshAfter': 'refresh after', + 'engine.actionPreview.pill.aiOptedOut': 'AI: opted out', + 'engine.actionPreview.pill.aiExposed': 'AI: exposed', + 'engine.actionPreview.locations': 'Locations:', + 'engine.actionPreview.requires': 'Requires:', + 'engine.actionPreview.visibleWhen': 'Visible when', + 'engine.actionPreview.disabledWhen': 'Disabled when', + 'engine.actionPreview.onClick': 'On click', + 'engine.actionPreview.firstAsks': 'First asks:', + 'engine.actionPreview.onSuccess': 'On success:', + 'engine.actionPreview.handler.none': 'No handler bound yet.', + 'engine.actionPreview.handler.url': 'Navigate to {target}', + 'engine.actionPreview.handler.flow': 'Run flow {target}', + 'engine.actionPreview.handler.modal': 'Open modal {target}', + 'engine.actionPreview.handler.api': 'Call API endpoint {target}', + 'engine.actionPreview.handler.form': 'Open form view {target} (/console/forms/{path})', + 'engine.actionPreview.handler.scriptBody': 'Run inline script body (L1 expression or L2 sandboxed JS).', + 'engine.actionPreview.handler.scriptNamed': 'Run named script {target}', + 'engine.actionPreview.handler.invoke': 'Invoke {target}', + 'engine.actionPreview.section.placement': 'Where it appears', + 'engine.actionPreview.capabilityGateNote': + 'Hidden from anyone who does not hold {capabilities} — the frames below show the placement, not who can see it.', + 'engine.actionPreview.section.testRequest': 'Test request', + 'engine.actionPreview.section.inputDialog': 'Input Dialog', + 'engine.actionPreview.section.resultDialog': 'Result Dialog', + 'engine.actionPreview.section.scriptBody': 'Script Body ({language})', + 'engine.actionPreview.dialog.ok': 'OK', + 'engine.actionPreview.param.carryOver': 'Carried over from the selected row (read-only)', + 'engine.actionPreview.param.fromRow': '(from selected row)', + 'engine.actionPreview.param.selectPlaceholder': 'Select {label}', + 'engine.actionPreview.param.recordIdPlaceholder': 'Record id for {label}', + 'engine.actionPreview.param.noReference': + 'No {reference} object is configured, so the record picker is unavailable and the dialog asks for a record id.', + 'engine.actionPreview.param.toggle': 'Toggle', + 'engine.actionPreview.param.noChoices': 'No choices authored yet — the dialog opens an empty picker.', + 'engine.actionPreview.param.searchObject': 'Search {object}…', + 'engine.actionPreview.param.searchRecords': 'Search records…', + 'engine.actionPreview.result.title': 'Result', + 'engine.actionPreview.result.acknowledge': 'I have saved this', + 'engine.actionPreview.result.fullJson': 'Renders full JSON response.', + 'engine.actionPreview.placement.sampleRecord': 'Sample record', + 'engine.actionPreview.placement.recordDetail': 'Record detail', + 'engine.actionPreview.placement.recordBody': '…record body…', + 'engine.actionPreview.placement.records': 'Records', + 'engine.actionPreview.placement.rows': 'row 1 · row 2 · row 3', + 'engine.actionPreview.placement.row': 'Row {n}', + 'engine.actionPreview.placement.section': 'Section', + 'engine.actionPreview.placement.sectionBody': '…section content…', + 'engine.actionPreview.placement.more': 'More', + 'engine.actionPreview.api.paramsOne': '+ {count} user-supplied param collected at runtime', + 'engine.actionPreview.api.paramsOther': '+ {count} user-supplied params collected at runtime', + 'engine.actionPreview.api.liveWarning': 'Sends a real {method} request to the live backend — may modify data.', + 'engine.actionPreview.api.sending': 'Sending…', + 'engine.actionPreview.api.send': 'Send test request', + 'engine.actionPreview.api.networkError': 'Network error', + 'engine.agentPreview.tryInChat': 'Try in chat', + 'engine.agentPreview.tryInChatTitle': 'Open the saved version of this agent in a new chat', + 'engine.agentPreview.empty': 'Fill in label, role, and instructions in the Form tab to see the agent preview.', + 'engine.agentPreview.active': 'Active', + 'engine.agentPreview.disabled': 'Disabled', + 'engine.agentPreview.temperature': 'temp {value}', + 'engine.agentPreview.maxTokens': 'max {count}t', + 'engine.agentPreview.instructions': 'Instructions', + 'engine.agentPreview.noInstructions': 'No system prompt set yet.', + 'engine.agentPreview.capabilities': 'Capabilities', + 'engine.agentPreview.skills': 'Skills', + 'engine.agentPreview.noSkills': 'No skills attached — this agent can reach no tools.', + 'engine.agentPreview.toolsNote': 'Tools come from the attached skills; grounding is described in the instructions.', + 'engine.agentPreview.planning': 'Planning', + 'engine.agentPreview.memory': 'Memory', + 'engine.agentPreview.guardrails': 'Guardrails', + 'engine.agentPreview.permissions': 'Permissions', + 'engine.agentPreview.defaults': 'Defaults in use.', + 'engine.agentPreview.noValues': 'No values set.', + 'engine.skillPreview.empty': 'Give the skill a name and at least an instructions block to see the preview.', + 'engine.skillPreview.active': 'Active', + 'engine.skillPreview.disabled': 'Disabled', + 'engine.skillPreview.model': 'model: {model}', + 'engine.skillPreview.instructions': 'Instructions', + 'engine.skillPreview.noInstructions': 'No instructions yet — the skill will contribute nothing to the prompt.', + 'engine.skillPreview.tools': 'Tools ({count})', + 'engine.skillPreview.noTools': 'No tools whitelisted.', + 'engine.skillPreview.wildcardNote': + 'Wildcards expand to many tools at runtime — review the matching set in the agent before activating.', + 'engine.skillPreview.triggerConditions': 'Trigger Conditions ({count})', + 'engine.skillPreview.col.field': 'Field', + 'engine.skillPreview.col.operator': 'Operator', + 'engine.skillPreview.col.value': 'Value', + 'engine.skillPreview.allMustHold': 'All conditions must hold (AND) for the skill to activate.', + 'engine.skillPreview.missing': 'missing', + 'engine.skillPreview.missingTitle': 'Required by SkillTriggerConditionSchema — this draft will be rejected on save.', + 'engine.toolPreview.openApiConsole': 'Open in API Console', + 'engine.toolPreview.openApiConsoleTitle': 'Open API Console to test invocation', + 'engine.toolPreview.empty': 'Set name, description, and parameters to see the tool preview.', + 'engine.toolPreview.inputParameters': 'Input Parameters', + 'engine.toolPreview.noParameters': 'This tool takes no input parameters.', + 'engine.toolPreview.exampleCall': 'Example LLM Call', + 'engine.toolPreview.outputSchema': 'Output Schema', + 'engine.toolPreview.col.name': 'Name', + 'engine.toolPreview.col.type': 'Type', + 'engine.toolPreview.col.description': 'Description', + 'engine.toolPreview.required': 'req', + 'engine.toolPreview.enum': 'enum: {values}', + 'engine.toolPreview.default': 'default:', + 'engine.toolPreview.format': 'format: {format}', + 'engine.jobPreview.empty': 'Set a schedule (cron / every / at) and a handler to see the job preview.', + 'engine.jobPreview.active': 'Active', + 'engine.jobPreview.paused': 'Paused', + 'engine.jobPreview.retries': 'retries: {count}', + 'engine.jobPreview.retriesBackoff': 'retries: {count} ({backoff} backoff)', + 'engine.jobPreview.timeout': 'timeout: {duration}', + 'engine.jobPreview.concurrency': 'concurrency: {count}', + 'engine.jobPreview.schedule': 'Schedule', + 'engine.jobPreview.every': 'Every', + 'engine.jobPreview.at': 'At', + 'engine.jobPreview.unparseable': 'unparseable', + 'engine.jobPreview.noSchedule': 'No schedule set — runs only when triggered manually.', + 'engine.jobPreview.nextRunOne': 'Next {count} run', + 'engine.jobPreview.nextRunOther': 'Next {count} runs', + 'engine.jobPreview.noFireTime': 'Could not derive any future fire time. Check the schedule syntax.', + 'engine.jobPreview.noUpcoming': 'No upcoming runs in the next 366 days.', + 'engine.jobPreview.inDelta': 'in {delta}', + 'engine.jobPreview.inNow': 'in now', + 'engine.jobPreview.handler': 'Handler', + 'engine.jobPreview.noHandler': 'No handler bound — the job will be a no-op.', + // objectui#10862 (slice 2: the admin previews and the CEL lint advisories) — + // the designer's own words in `AppPreview`, `BookPreview`, + // `DatasourcePreview`, `EmailTemplatePreview`, `PermissionPreview`, + // `PositionPreview` and `TranslationPreview`, read in the `locale` each + // preview is handed. Each en row is the literal it replaced; a `{token}` a + // row names in code font (`{navigation}`, `{include}`, …) is filled with + // that code span, so the sentence keeps its order in both languages. Author + // data (labels, names, descriptions, doc names, variable names, config + // values), identifiers (nav kinds, enum values, access-scope tokens, the + // C / R / U / D column letters, `SSL`) and notation (`∅`, `#3`, `[2]`, the + // `3/8 (38%)` ratio) show as written. + 'engine.appPreview.openTitle': 'Open this app in a new tab', + 'engine.appPreview.open': 'Open', + 'engine.appPreview.homeFirst': 'Home: opens the first navigation item', + 'engine.appPreview.homeNone': 'Home: no navigation item yields a route yet', + 'engine.appPreview.empty': + "No top-level nav items. Add {navigation} entries in the Form tab to populate the app's navigation.", + 'engine.appPreview.unnamed': '(unnamed)', + 'engine.appPreview.noTypeTitle': + 'Every navigation item needs a `type` discriminator — this entry will be rejected on save.', + 'engine.appPreview.noType': 'no type', + // objectui#11027 — the heading over the app's navigation areas (`AppSchema.areas`). + 'engine.appPreview.areas': 'Areas', + 'engine.emailTemplatePreview.empty': 'Fill in the subject / body in the Form tab to see a preview.', + 'engine.emailTemplatePreview.emptyBody': '(empty body)', + 'engine.emailTemplatePreview.from': 'From', + 'engine.emailTemplatePreview.to': 'To', + 'engine.emailTemplatePreview.subject': 'Subject', + 'engine.emailTemplatePreview.frameTitle': 'Email preview', + 'engine.emailTemplatePreview.variables': 'Variables', + 'engine.emailTemplatePreview.noVariables': 'No {var} placeholders found.', + 'engine.emailTemplatePreview.samplePlaceholder': 'sample for {name}', + 'engine.permissionPreview.empty': 'Grant at least one object, system, or tab permission to see the matrix.', + 'engine.permissionPreview.kind.default': 'Default Permission Set', + 'engine.permissionPreview.kind.set': 'Permission Set', + 'engine.permissionPreview.pill.objects': '{count} objects', + 'engine.permissionPreview.pill.systemPerms': '{count} system perms', + 'engine.permissionPreview.pill.tabs': '{count} tabs', + 'engine.permissionPreview.pill.rls': '{count} RLS rules', + 'engine.permissionPreview.section.objects': 'Object Permissions', + 'engine.permissionPreview.section.fields': 'Field-Level Overrides', + 'engine.permissionPreview.section.system': 'System Permissions', + 'engine.permissionPreview.section.tabs': 'Tab Visibility', + 'engine.permissionPreview.col.object': 'Object', + 'engine.permissionPreview.cap.create': 'Create', + 'engine.permissionPreview.cap.read': 'Read', + 'engine.permissionPreview.cap.edit': 'Edit', + 'engine.permissionPreview.cap.delete': 'Delete', + 'engine.permissionPreview.cap.export': 'Export', + 'engine.permissionPreview.cap.transfer': 'Transfer', + 'engine.permissionPreview.cap.viewAll': 'View All', + 'engine.permissionPreview.cap.modifyAll': 'Modify All', + 'engine.permissionPreview.col.scopeTitle': 'Read / write access depth (ADR-0057)', + 'engine.permissionPreview.col.scope': 'Scope', + 'engine.permissionPreview.readScope': 'Read scope', + 'engine.permissionPreview.writeScope': 'Write scope', + 'engine.permissionPreview.scopeDefault': 'default', + 'engine.permissionPreview.fls.hidden': 'hidden', + 'engine.permissionPreview.fls.editable': 'editable', + 'engine.permissionPreview.sanityOne': '{count} sanity check failed', + 'engine.permissionPreview.sanityOther': '{count} sanity checks failed', + 'engine.permissionPreview.warn.editWithoutRead': 'Edit granted without Read (record updates will fail).', + 'engine.permissionPreview.warn.deleteWithoutRead': 'Delete granted without Read.', + 'engine.permissionPreview.warn.modifyAllWithoutViewAll': + 'Modify All without View All — modifications may target invisible records.', + 'engine.permissionPreview.warn.writeWiderThanRead': + 'Write scope ({write}) is wider than read scope ({read}) — edits may target invisible records.', + 'engine.permissionPreview.granted': 'granted', + 'engine.permissionPreview.notGranted': 'not granted', + 'engine.permissionPreview.legend.bypass': 'bypass (View/Modify All)', + 'engine.permissionPreview.legend.key': 'C R U D = CRUD · E T = Export/Transfer · V* M* = View/Modify All', + 'engine.positionPreview.empty': 'Give the position a name to see the preview.', + 'engine.positionPreview.noAccess': "Positions don't grant data access on their own.", + 'engine.positionPreview.bind': + 'Bind {name} to one or more {permissionSets} to control CRUD-VAMA, field access, and tab visibility. Record visibility comes from the business-unit tree and sharing rules, not from the position.', + 'engine.positionPreview.permissionSets': 'Permission Sets', + // objectui#13304 — a position that declares `permissionSets` (ADR-0131 D4). + 'engine.positionPreview.grantsVia': 'This position grants access through these {permissionSets}:', + 'engine.positionPreview.visibility': + 'Record visibility comes from the business-unit tree and sharing rules, not from the position.', + // objectui#10202 — the read-only picklist page (the kind is package-owned). + 'engine.picklistPreview.empty': 'This picklist has no name.', + 'engine.picklistPreview.readOnly': + 'Read-only: a picklist is owned by the package that declares it, and is not created, edited or deleted here.', + 'engine.picklistPreview.ownedBy': 'Owning package', + 'engine.picklistPreview.options': 'Options', + 'engine.picklistPreview.colLabel': 'Label', + 'engine.picklistPreview.colValue': 'Value', + 'engine.picklistPreview.noOptions': 'This picklist declares no options.', + 'engine.picklistPreview.extensions': 'Extensions', + 'engine.picklistPreview.extensionsHint': 'Options other packages add to this picklist.', + 'engine.picklistPreview.addedBy': 'Added by {package}', + 'engine.picklistPreview.noExtensions': 'No package extends this picklist.', + 'engine.picklistPreview.extensionsLoading': 'Loading extensions…', + 'engine.picklistPreview.extensionsFailed': + 'The installed packages could not be read, so the extensions of this picklist are unknown.', + // objectui#10862 (slice 3) — the object / data previews (`ObjectPreview`, + // `DatasetPreview`, `ViewPreview`, `ReportPreview`, `PagePreview`, + // `PageBlockCanvas` and its add-block picker, `SourcePageEditor`, + // `ValidationPreview`, the view column manager) and the federated + // datasource panel, each en row the literal it replaces. Author data (names, + // labels, messages, tags, region and slot names, CEL and regex sources) and + // producer text (the error a renderer threw, an engine or HTTP message) stay + // as written. + 'engine.objectPreview.renderFailed': "The form designer couldn't be rendered. Check the Form tab.", + 'engine.datasetPreview.pickObject': 'Pick a base object', + 'engine.datasetPreview.pickObjectHint': "Set the dataset's `object` to preview it against live data.", + 'engine.datasetPreview.addMeasure': 'Add a measure', + 'engine.datasetPreview.addMeasureHint': + 'A dataset needs at least one measure (e.g. revenue = sum(amount)) to preview.', + 'engine.datasetPreview.run': 'Run preview', + 'engine.datasetPreview.measureOne': '{count} measure', + 'engine.datasetPreview.measureOther': '{count} measures', + 'engine.datasetPreview.dimensionOne': '{count} dimension', + 'engine.datasetPreview.dimensionOther': '{count} dimensions', + 'engine.datasetPreview.notInstalled': 'Analytics capability not installed', + 'engine.datasetPreview.notInstalledHint': + "This deployment has no analytics service, so dataset previews can't run. Install @objectstack/service-analytics and mount AnalyticsServicePlugin — the dataset definition itself is fine.", + 'engine.datasetPreview.noRows': 'No rows', + 'engine.datasetPreview.noRowsHint': 'The dataset returned no rows for the current scope.', + 'engine.datasetPreview.chartFailed': + "Couldn't render the chart for this result — the table below still shows the data.", + 'engine.datasetPreview.ratioAxis': 'Ratio measures ({measures}) use the right axis.', + 'engine.viewPreview.schemaFailed': + "The view's `type` may not be registered, or required fields are missing.", + 'engine.viewPreview.noObject': + 'This view has no object binding yet. Set the bound {object} in the right panel to fetch live data and field options.', + 'engine.viewPreview.formFailed': "The form view references an object or field that doesn't resolve.", + 'engine.viewPreview.listFailed': "The view references an object or field that doesn't resolve.", + 'engine.reportPreview.renderFailed': + "The Report references a dataset/measure that doesn't resolve, or its config is incomplete.", + 'engine.reportPreview.loading': 'Loading report renderer…', + 'engine.reportPreview.joinedEmpty': 'Add a block to preview this joined report', + 'engine.reportPreview.joinedEmptyHint': + 'A joined report stacks dataset-bound blocks. Add a block and bind its dataset + measures in the right panel to start designing.', + 'engine.reportPreview.empty': 'Bind a dataset to preview this report', + 'engine.reportPreview.emptyHint': + "Since the 9.0 single-form cutover a report renders its dataset's measures (values) grouped by dimensions (rows). Choose a Dataset in the right panel to start designing.", + 'engine.pagePreview.interfaceFailed': + "The interface page references a source object/view that isn't available.", + 'engine.pagePreview.schemaFailed': + "The Page schema is incomplete or references a component that hasn't been registered yet.", + 'engine.pagePreview.previewRecord': 'Preview record', + 'engine.pagePreview.sampleOne': '{count} sample', + 'engine.pagePreview.sampleOther': '{count} samples', + 'engine.pagePreview.recordFallback': '(record)', + 'engine.pagePreview.addComponents': 'Add components to the page to see a preview.', + 'engine.pagePreview.blockN': 'block {n}', + 'engine.validationPreview.empty': 'Give the validation a name to see the preview.', + 'engine.validationPreview.active': 'Active', + 'engine.validationPreview.disabled': 'Disabled', + 'engine.validationPreview.priority': 'priority {priority}', + 'engine.validationPreview.on': 'on {events}', + 'engine.validationPreview.noMessage': 'no message set', + 'engine.validationPreview.tags': 'Tags', + 'engine.validationPreview.removed.unique': + 'Uniqueness is a unique INDEX, not a validation rule: declare it on the object as `indexes: [{ fields: [...], unique: \'organization\' }]` — one holder per organization — or `unique: \'global\'` for one holder across the installation (or `unique: true` on the field). A SELECT-then-INSERT rule is inherently racy (TOCTOU); a database constraint is not.', + 'engine.validationPreview.removed.async': + 'Remote/async validation is a form-layer concern — on the server write path it is an SSRF and latency hazard. Keep it in the form, or enforce the underlying invariant with a unique index or a lifecycle hook.', + 'engine.validationPreview.removed.custom': + 'Arbitrary validation code belongs in a `beforeInsert` / `beforeUpdate` lifecycle hook — the typed, supported extension point.', + 'engine.validationPreview.notARule': 'Not a validation rule', + 'engine.validationPreview.removedType': + '{type} was removed from ValidationRuleSchema — this rule cannot be saved.', + 'engine.validationPreview.condition': 'Condition', + 'engine.validationPreview.when': 'When', + 'engine.validationPreview.thenApply': 'Then apply', + 'engine.validationPreview.noNested': 'No nested rule set — this rule enforces nothing.', + 'engine.validationPreview.otherwiseApply': 'Otherwise apply', + 'engine.validationPreview.transitions': 'Transitions', + 'engine.validationPreview.transitionsOn': 'Transitions on {field}', + 'engine.validationPreview.noTransitions': 'No transitions declared.', + 'engine.validationPreview.deadEnd': '∅ (dead-end)', + 'engine.validationPreview.createdIn': 'May be created in', + 'engine.validationPreview.format': 'Format', + 'engine.validationPreview.formatOn': 'Format on {field}', + 'engine.validationPreview.builtIn': 'Built-in:', + 'engine.validationPreview.regex': 'Regex:', + 'engine.validationPreview.noFormat': 'No format or regex set.', + 'engine.validationPreview.fieldsInvolved': 'Fields involved', + 'engine.validationPreview.none': 'none', + 'engine.validationPreview.crossFieldCondition': 'Cross-field condition', + 'engine.validationPreview.jsonSchemaOn': 'JSON Schema on {field}', + 'engine.validationPreview.noJsonSchema': 'No JSON Schema set.', + 'engine.validationPreview.rule': 'Rule', + 'engine.validationPreview.unknownType': 'Unknown rule type "{type}". Showing common fields only.', + 'engine.validationPreview.noType': 'no type', + 'engine.validationPreview.noExpression': 'No expression set.', + // ── StudioDesignSurface (ADR-0080 WYSIWYG design surface) ────────────── + // Shared chrome + 'engine.studio.cancel': 'Cancel', + 'engine.studio.creating': 'Creating…', + // objectui#11792 — CreateItemDialog's preview of the name the item is saved + // under, shown when the caller supplies that rule (an object's namespace prefix). + 'engine.studio.createItem.savedAs': 'Saved as', + 'engine.studio.createItem.notDerived': 'The display name has no Latin letters to make an identifier from. Type one: lowercase letters, digits and underscores.', + // `383502b23` — this is the CONFIRM control of the shared create dialog. + // Never give it a name that shares a leading run with the affordance that + // OPENS that dialog (`engine.studio.app.create`, `Create app` — and the + // dialog itself is titled after it, which is correct). It used to read + // `Create (save as draft)`, so the only handle it offered a by-name caller + // was `Create`, which also matched all three openers (toolbar, Interfaces + // empty state, Interfaces rail): nothing could address THIS control, the one + // that actually creates. While the dialog is open every opener is + // `aria-hidden` behind Radix's modal overlay, so a by-name click there + // dismisses the dialog with no error and nothing created. Pinned by + // `studio-design/StudioDesignSurface.createAppAccessibleName.test.tsx`. + 'engine.studio.createDraft': 'Save as draft', + 'engine.studio.saveDraft': 'Save draft', + 'engine.studio.autoSaving': 'Saving…', + // Standard create-dialog field labels (shared by object / app / flow / permission). + 'engine.studio.app.nameLabel': 'App name', + 'engine.studio.app.idLabel': 'Identifier', + 'engine.studio.pkg.manage': 'Package info & settings', + // objectui#7881 — the read SUCCEEDED and the package is simply not in the + // list; the sheet is not opened, and this says so instead of nothing. + 'engine.studio.pkg.manageMissing': + 'Package {id} is not in the installed list — it may have been deleted or uninstalled elsewhere.', + // objectui#7907 — the managed-snapshot refresh that follows a lifecycle + // action failed. The sheet is an ACTION surface whose verb is derived from + // the record it holds, so it is closed rather than left re-armed with the + // pre-action one; this names that consequence and carries the server's own + // words inside it (one report, on the shared sonner id). + 'engine.studio.pkg.manageRefreshFailed': + 'Package {id} could not be refreshed after that change — closing the panel rather than showing the pre-change record as current. {error}', + 'engine.studio.publish': 'Publish', + 'engine.studio.loading': 'Loading…', + 'engine.studio.loadFailed': 'Failed to load', + // objectui#11785 — a refused save, in the author's words. The strip shows one + // of these sentences; the raw refusal (field paths, codes) stays under + // `engine.studio.refusal.details`, and `show` opens the input it names. + 'engine.studio.refusal.choiceWithoutOptions': + 'Changes not saved: the field “{field}” needs at least one option. Add an option, or change the field to a type that is not a choice.', + 'engine.studio.refusal.relationshipWithoutTarget': + 'Changes not saved: the field “{field}” does not say which object it links to. Pick the object, or change the field to a type that is not a relationship.', + 'engine.studio.refusal.issue': 'Changes not saved. Check {where} — {problem}', + 'engine.studio.refusal.more': '({count} more under Details)', + 'engine.studio.refusal.unlocated': 'Changes not saved: the server refused this draft. Details say what to change.', + 'engine.studio.refusal.field': 'the field “{field}”', + 'engine.studio.refusal.step': 'the step “{step}”', + 'engine.studio.refusal.stepInput': '{input} on the step “{step}”', + 'engine.studio.refusal.navItem': 'the navigation item “{item}”', + 'engine.studio.refusal.navItemInput': '{input} on the navigation item “{item}”', + 'engine.studio.refusal.details': 'Details', + 'engine.studio.refusal.show': 'Show me', + // objectui#11786 — an edit held, not refused: an incomplete-but-normal body + // (a choice field before its options, a relationship before its target, a + // step before its required inputs) stays on screen unsent. Each `needs*` row + // is a clause the held line and the Publish refusal both build on. + 'engine.studio.held.needsOptions': 'the field “{field}” needs at least one option', + 'engine.studio.held.needsTarget': 'the field “{field}” needs the object it links to', + 'engine.studio.held.needsInput': '{input} on the step “{step}” needs a value', + 'engine.studio.held.needsCondition': 'the rule “{rule}” needs a condition', + 'engine.studio.held.needsThenCondition': 'the “Then” rule of “{rule}” needs a condition', + 'engine.studio.held.needsRuleField': 'the rule “{rule}” needs a field', + 'engine.studio.held.needsRuleMessage': 'the rule “{rule}” needs an error message', + // objectui#11910 — a dashboard widget held until it is bound; `input` is the + // widget inspector's own label for it (Dataset, Values (measures)). + 'engine.studio.held.widgetNeedsInput': '{input} on the widget “{widget}” needs a value', + 'engine.studio.held.line': 'Not saved yet: {clause}. Your changes are kept here and saved once it is filled in.', + 'engine.studio.held.publish': 'Not published: {clause}, so that change is not saved yet. Finish it, then publish.', + 'engine.studio.held.inputHint': 'Required. Changes are saved once this has a value.', + 'engine.studio.unpublishedDraft': 'Unpublished draft', + // objectui#12725 — the item header's draft line: this item has a pending + // draft, and {count} is the package's pending count (the Changes badge's). + 'engine.studio.itemDraftPending': 'Draft · {count} unpublished', + 'engine.studio.itemDraftPendingTitle': + 'This item has an unpublished draft. The package has {count} unpublished change(s); Publish puts them live together.', + 'engine.studio.unpublished': 'Unpublished', + 'engine.studio.new': 'New', + 'engine.studio.edit': 'Edit', + 'engine.studio.done': 'Done', + 'engine.studio.close': 'Close', + 'engine.studio.deselect': 'Clear selection', + 'engine.studio.toggleRail': 'Toggle sidebar', + // objectui#12513 — the header's first button and the package switcher's + // first row: both return to the Studio landing (`/studio`). + 'engine.studio.landingHome': 'Studio home', + // objectui#11795 — the one line a phone shows under the Studio header. + 'engine.studio.desktopHint': 'Studio is built for a desktop screen.', + 'engine.studio.aiCopilot': 'AI copilot', + // objectui#12725 — the Studio right slot: properties OR the copilot. + 'engine.studio.slot.label': 'Right panel', + 'engine.studio.slot.switchHint': 'Switch with ⌘. (Ctrl+.)', + 'engine.studio.slot.collapse': 'Hide the right panel', + 'engine.studio.slot.expand': 'Show the right panel', + 'engine.studio.slot.backToChat': 'Back to the conversation', + 'engine.studio.slot.planPending': 'a plan awaits your approval', + 'engine.studio.slot.openFullPage': 'Open full page', + 'engine.studio.pillar.data': 'Data', + 'engine.studio.pillar.automations': 'Automations', + 'engine.studio.pillar.interfaces': 'Interfaces', + 'engine.studio.pillar.access': 'Access', + // Header · package-level publish + app bridge + 'engine.studio.changes': 'Changes', + 'engine.studio.publishTitle': 'Confirm and publish all pending drafts at once (whole package · one atomic release)', + 'engine.studio.publishNoneTitle': 'No drafts pending publish', + 'engine.studio.publishedAll': 'Published all drafts in this package (one atomic release)', + // objectui#11807 — the publish toast names how many items went live. + 'engine.studio.publishedAllCount': 'Published {count} items in this package (one atomic release)', + 'engine.studio.publishedAllCountOne': 'Published {count} item in this package (one atomic release)', + // objectui#12929 — the batch door's backfill receipt, one clause per field + // the review sheet named: the rows the server wrote, and why a field stopped. + 'engine.studio.backfillWritten': 'Default backfilled into {field}: {count} record(s)', + 'engine.studio.backfillStopped': 'Default backfill of {field} stopped after {count} record(s): {error}', + 'engine.studio.app.open': 'Open app', + 'engine.studio.app.openTitle': 'Open app “{label}” (the published front-end)', + 'engine.studio.app.create': 'Create app', + 'engine.studio.app.noneTitle': 'This package has no app (front-end) yet — create one', + 'engine.studio.app.willOpenAfterPublish': 'After publishing, this becomes “Open app”', + 'engine.studio.app.pending': 'App “{label}” pending publish', + 'engine.studio.app.namePlaceholder': 'App name (e.g. Order Center)', + 'engine.studio.app.idPlaceholder': 'Identifier (e.g. orders_app)', + 'engine.studio.app.savedDraft': 'App “{label}” saved as draft — open it after publishing', + // Package switcher + 'engine.studio.pkg.switchTitle': 'Switch / create package', + 'engine.studio.pkg.readonly': 'Read-only', + 'engine.studio.pkg.readonlyHint': 'Read-only package — switch to or create a writable package to edit.', + // objectui#12453 — the item header's read-only link, every pillar. + 'engine.studio.pkg.readonlyLink': 'Read-only · Customize with an overlay', + 'engine.studio.app.scaffoldNav': "Add this package's objects as menu items", + 'engine.studio.landing.title': 'App Builder', + 'engine.studio.landing.description': 'Design objects, forms, automations and interfaces inside a writable package; edits save as drafts and the whole package publishes in one release. Code and installed packages are read-only: browse them here, and customize them with an environment overlay.', + 'engine.studio.landing.mineHeading': 'My packages (writable)', + 'engine.studio.landing.noneWritable': 'No writable packages yet — create one to start.', + 'engine.studio.landing.dup': 'Duplicate', + 'engine.studio.landing.dupTitle': 'Duplicate this package into a new writable copy (like “duplicate base”)', + 'engine.studio.landing.dupDefaultName': '{name} copy', + 'engine.studio.landing.dupNamePlaceholder': 'Copy name', + 'engine.studio.landing.dupIdPlaceholder': 'Copy package ID', + 'engine.studio.landing.dupGo': 'Duplicate and open the builder', + 'engine.studio.landing.dupCreated': 'Duplicated into writable package “{name}”', + 'engine.studio.landing.installedHeading': 'Installed (read-only · browsable)', + // objectui#11808 — the routes a read-only package does have (ADR-0070 D2, + // ADR-0005): an environment overlay, and the marketplace where the runtime has one. + 'engine.studio.landing.readonlyHint': 'Code and installed packages are read-only, so they are not edited or duplicated here. Customize one with an environment overlay, for the metadata types that accept one.', + // objectui#12513 — the link leaves Studio for Setup, and says so. + 'engine.studio.landing.overlay': 'Open in Setup', + 'engine.studio.landing.overlayTitle': 'Open this package’s metadata: each type shows whether it accepts an environment overlay', + 'engine.studio.landing.marketplace': 'Or install a template app from the marketplace', + // objectui#11553 — the package-less scope: the organization's own flows. + 'engine.studio.landing.orgHeading': 'Not in a package', + 'engine.studio.org.name': 'Organization flows', + 'engine.studio.org.hint': 'Flows that belong to no package', + 'engine.studio.org.description': 'The organization’s own flows that belong to no package, such as a clone of a packaged flow. They open editable.', + 'engine.studio.org.none': 'No flows outside a package yet. Clone a packaged flow in Setup › Packaged automation to edit the copy here.', + // objectui#12513 — the empty block's way to that Setup list: a link, since this scope creates nothing. + 'engine.studio.org.openSetup': 'Open in Setup', + 'engine.studio.publishedAllFlows': 'Published {count} package-less flow drafts', + 'engine.studio.publishedAllFlowsOne': 'Published {count} package-less flow draft', + 'engine.studio.designer.search': 'Search…', + 'engine.studio.designer.select': 'Select…', + 'engine.studio.designer.pickDate': 'Pick a date…', + 'engine.studio.designer.fieldAria': '{label} — click to edit properties, drag to reorder', + // objectui#11781 — a read-only package's card only opens the greyed inspector. + 'engine.studio.designer.fieldAriaReadOnly': '{label} — click to view properties', + // objectui#11872 — a field card's `aria-roledescription`: the role read after + // its label, in place of dnd-kit's English default `sortable`. + 'engine.studio.designer.fieldRole': 'draggable field', + 'engine.studio.designer.groupUp': 'Move group up', + 'engine.studio.designer.groupDown': 'Move group down', + 'engine.studio.designer.groupDelete': 'Delete group', + 'engine.studio.designer.groupDeleteTitle': 'Delete group (its fields move to Ungrouped)', + 'engine.studio.designer.dropHere': 'Drag fields here', + 'engine.studio.designer.ungrouped': 'Ungrouped', + 'engine.studio.designer.newGroup': 'New group', + // objectui#12453 — a read-only field card's title: what a click still does. + 'engine.studio.designer.fieldTitleReadOnly': 'Click a field to view its properties', + // objectui#12451 — the Form tab toolbar's hint. + 'engine.studio.designer.toolbarHint': 'Drag fields to arrange · click a field to edit its properties', + 'engine.studio.designer.addGroup': 'Add group', + // What the form designer's drag live region says (objectui#11802): labels, + // never the canvas's internal ids, and the place as "N of M" in the group. + 'engine.studio.formDnd.instructions': + 'To pick up a field, press Space or Enter. While dragging, use the arrow keys to move it. Press Space or Enter again to drop it in its new place, or press Escape to cancel.', + 'engine.studio.formDnd.start': 'Picked up {field}. It is in {group}, position {position} of {total}.', + 'engine.studio.formDnd.over': '{field} is over {group}, position {position} of {total}.', + 'engine.studio.formDnd.overNone': '{field} is not over a group.', + 'engine.studio.formDnd.end': '{field} moved to {group}, position {position} of {total}.', + 'engine.studio.formDnd.endNone': '{field} was dropped outside the groups and is back in {group}, position {position} of {total}.', + 'engine.studio.formDnd.cancel': 'Dragging cancelled. {field} is back in {group}, position {position} of {total}.', + 'engine.studio.rules.title': 'Validation rules', + 'engine.studio.rules.none': 'No validation rules yet.', + 'engine.studio.rules.newButton': 'New validation rule', + 'engine.studio.rules.noMessage': '(no message)', + 'engine.studio.rules.notSaved': 'Not saved — needs a condition', + 'engine.studio.rules.notSavedField': 'Not saved — needs a field', + 'engine.studio.rules.notSavedMessage': 'Not saved — needs a message', + 'engine.studio.rules.structured': '“{type}” rules carry structured configuration (state-machine transitions / format constraints etc.) and cannot be edited here yet — maintain them in the code package. Message: {message}', + 'engine.studio.rules.none2': '(none)', + 'engine.studio.rules.deleteRule': 'Delete rule', + 'engine.studio.rules.nameLabel': 'Rule name (snake_case, unique)', + 'engine.studio.rules.messageLabel': 'Error message (shown to the user when validation fails)', + 'engine.studio.rules.messagePlaceholder': 'e.g. Completion date is required when status is Done', + 'engine.studio.rules.celPre': 'Fail condition (CEL) — when the condition is ', + 'engine.studio.rules.celTrue': 'true', + 'engine.studio.rules.celMid': ', the save is rejected with the message above.', + // A conditional rule's `when`: it says when the `then` rule applies. + 'engine.studio.rules.whenPre': 'Guard (CEL) — when the condition is ', + 'engine.studio.rules.whenMid': ', the “Then” rule below applies; otherwise the “Otherwise” rule, if one is set.', + 'engine.studio.rules.severity': 'Severity', + 'engine.studio.rules.severityError': 'error (rejects the save)', + 'engine.studio.rules.enabled': 'Enabled', + 'engine.studio.rules.delete': 'Delete', + 'engine.studio.rules.type': 'Type', + 'engine.studio.rules.newType': 'New validation type', + 'engine.studio.rules.typeScript': 'Script — CEL fail condition', + 'engine.studio.rules.typeCrossField': 'Cross-field — CEL over multiple fields', + 'engine.studio.rules.typeStateMachine': 'State machine — allowed transitions', + 'engine.studio.rules.typeFormat': 'Format — regex / built-in format', + 'engine.studio.rules.typeJsonSchema': 'JSON schema — validate a JSON field', + 'engine.studio.rules.typeConditional': 'Conditional — apply a rule when a guard holds', + 'engine.studio.rules.label': 'Label (optional)', + 'engine.studio.rules.field': 'Field', + 'engine.studio.rules.statusField': 'Status field', + 'engine.studio.rules.fields': 'Participating fields', + 'engine.studio.rules.noFields': 'This object has no fields yet.', + 'engine.studio.rules.pickField': '— pick a field —', + 'engine.studio.rules.format': 'Built-in format', + 'engine.studio.rules.formatNone': '— none —', + 'engine.studio.rules.regex': 'Regex pattern', + 'engine.studio.rules.transitions': 'Allowed transitions (from → next states)', + 'engine.studio.rules.transitionFrom': 'From state', + 'engine.studio.rules.transitionTo': 'Next states (comma-separated)', + 'engine.studio.rules.addTransition': 'Add transition', + 'engine.studio.rules.jsonSchema': 'JSON Schema', + 'engine.studio.rules.invalidJson': 'Invalid JSON', + 'engine.studio.rules.then': 'Then — rule applied when the condition holds (JSON)', + 'engine.studio.rules.otherwise': 'Otherwise — rule applied when it does not (JSON, optional)', + 'engine.studio.rules.events': 'Runs on', + 'engine.studio.rules.event.insert': 'Create', + 'engine.studio.rules.event.update': 'Update', + 'engine.studio.rules.priority': 'Priority', + // objectui#11861 — the New menu's starting points, and the type list under Advanced. + 'engine.studio.rules.presets': 'Common rules', + 'engine.studio.rules.advanced': 'Advanced', + 'engine.studio.rules.preset.endAfterStart': 'End date on or after start date', + 'engine.studio.rules.preset.endAfterStartNeeds': 'Needs two date fields on this object.', + 'engine.studio.rules.preset.endAfterStartMessage': '{end} must be on or after {start}.', + 'engine.studio.rules.preset.notNegative': 'Number can’t be negative', + 'engine.studio.rules.preset.notNegativeNeeds': 'Needs a number field on this object.', + 'engine.studio.rules.preset.notNegativeMessage': '{field} can’t be negative.', + 'engine.studio.rules.preset.rejectWhen': 'Reject the save when…', + 'engine.studio.rules.preset.rejectWhenHint': 'Build your own condition, no code needed.', + 'engine.studio.rules.preset.usesOne': 'Uses “{field}”.', + 'engine.studio.rules.preset.usesTwo': 'Uses “{start}” and “{end}”.', + 'engine.studio.pkg.writable': 'Writable', + 'engine.studio.pkg.heading': 'Packages (apps)', + 'engine.studio.pkg.none': 'No app packages yet', + 'engine.studio.pkg.createWritable': 'Create writable package', + 'engine.studio.pkg.new': 'New package (writable base)', + 'engine.studio.pkg.created': 'Package {name} created (writable)', + // Nav item inspector (Interfaces pillar) + 'engine.studio.nav.selectItem': 'Select a menu item on the left.', + 'engine.studio.nav.label': 'Label', + 'engine.studio.nav.linkObject': 'Link to object', + 'engine.studio.nav.chooseObject': '— Choose object —', + 'engine.studio.nav.boundHint': 'This menu item opens that object’s record list.', + 'engine.studio.nav.unboundHint': 'Choose an object; the menu item will open its record list.', + 'engine.studio.nav.noObjects': 'This package has no objects yet — create one in the Data pillar first.', + 'engine.studio.nav.eyebrow': 'Nav item · {kind}', + 'engine.studio.nav.icon': 'Icon', + 'engine.studio.nav.visibleWhen': 'Visible when', + // objectui#12725 — the copilot's draft marks on a field or a view setting it changed. + 'engine.studio.draftMark.chip': 'Draft', + 'engine.studio.draftMark.sourceLabel': 'Source', + 'engine.studio.draftMark.source': 'Added as a draft by the AI copilot {when}', + 'engine.studio.draftMark.sourceJustNow': 'Added as a draft by the AI copilot just now', + 'engine.studio.draftMark.sourceItems': '{items}: {count} added as a draft by the AI copilot {when}', + 'engine.studio.draftMark.sourceItemsJustNow': '{items}: {count} added as a draft by the AI copilot just now', + 'engine.studio.draftMark.item': '"{label}"', + 'engine.studio.draftMark.minutesAgo': '{n} min ago', + 'engine.studio.draftMark.hoursAgo': '{n} h ago', + 'engine.studio.draftMark.daysAgo': '{n} d ago', + // objectui#11823 (steps 2 and 3) — the Interfaces pillar's New dashboard / New report / New page. + 'engine.studio.interfaces.create.menuTitle': 'Create a dashboard, a report or a page in this package', + 'engine.studio.interfaces.create.dashboard': 'New dashboard', + 'engine.studio.interfaces.create.report': 'New report', + 'engine.studio.interfaces.create.description': + 'Saved as a draft in this package, linked from the navigation of {app}, and opened on the canvas.', + 'engine.studio.interfaces.create.dashboardNameLabel': 'Dashboard name', + 'engine.studio.interfaces.create.reportNameLabel': 'Report name', + 'engine.studio.interfaces.create.dashboardNamePlaceholder': 'Dashboard name (e.g. Sales overview)', + 'engine.studio.interfaces.create.reportNamePlaceholder': 'Report name (e.g. Revenue by region)', + 'engine.studio.interfaces.create.idLabel': 'Identifier', + 'engine.studio.interfaces.create.dashboardIdPlaceholder': 'Identifier (e.g. sales_overview)', + 'engine.studio.interfaces.create.reportIdPlaceholder': 'Identifier (e.g. revenue_by_region)', + 'engine.studio.interfaces.create.dashboardTaken': + 'A dashboard with the identifier “{name}” already exists. Choose another identifier.', + 'engine.studio.interfaces.create.reportTaken': + 'A report with the identifier “{name}” already exists. Choose another identifier.', + 'engine.studio.interfaces.create.dataset': 'Dataset', + 'engine.studio.interfaces.create.datasetPlaceholder': 'Choose a dataset…', + 'engine.studio.interfaces.create.measure': 'Measure', + 'engine.studio.interfaces.create.measurePlaceholder': 'Choose a measure…', + 'engine.studio.interfaces.create.reportHint': + 'A report shows measures of one dataset. Add more measures and groupings in the Properties panel once it is created.', + 'engine.studio.interfaces.create.noDatasets': + 'There is no dataset to report on yet. A report shows the measures of a dataset, so create a dataset first.', + 'engine.studio.interfaces.create.datasetsFailed': 'The datasets could not be loaded: {error}', + 'engine.studio.interfaces.create.noMeasures': 'This dataset declares no measures, so a report cannot show it.', + 'engine.studio.interfaces.create.needsBinding': 'Choose the dataset this report reads, and one of its measures.', + 'engine.studio.interfaces.create.page': 'New page', + 'engine.studio.interfaces.create.pageNameLabel': 'Page name', + 'engine.studio.interfaces.create.pageNamePlaceholder': 'Page name (e.g. Team handbook)', + 'engine.studio.interfaces.create.pageIdPlaceholder': 'Identifier (e.g. team_handbook)', + 'engine.studio.interfaces.create.pageTaken': + 'A page with the identifier “{name}” already exists. Choose another identifier.', + 'engine.studio.interfaces.create.pageKind': 'Written in', + 'engine.studio.interfaces.create.pageKindHtml': 'HTML', + 'engine.studio.interfaces.create.pageKindReact': 'React', + 'engine.studio.interfaces.create.pageKindHtmlHint': + 'Built from the platform’s components and HTML tags, parsed and never run. The page opens on its source beside a live preview.', + 'engine.studio.interfaces.create.pageKindReactHint': + 'Real React code, run when the page renders, for state, effects and event handlers. The page opens on its source beside a live preview.', + 'engine.studio.interfaces.create.pageStarter': 'A new page. Write its content in the source editor.', + // Actions view + 'engine.studio.actions.none': 'No actions on this object.', + 'engine.studio.actions.newButton': 'New action', + 'engine.studio.actions.editorMissing': + 'No action editor is registered in this session, so this action’s properties cannot be edited here.', + 'engine.studio.actions.newLabel': 'New action', + 'engine.studio.actions.delete': 'Delete', + // objectui#11861 — the New menu's starting points (`actionPresets.ts`). + 'engine.studio.actions.presets': 'Common actions', + 'engine.studio.actions.notSaved': 'Not saved — needs “{input}”', + 'engine.studio.actions.heldNeeds': 'the action “{action}” needs “{input}”', + 'engine.studio.actions.preset.change': 'Change a picklist field', + 'engine.studio.actions.preset.changeHint': 'Asks for a new “{field}”, then saves it.', + 'engine.studio.actions.preset.changeNeeds': 'Needs a picklist field on this object.', + 'engine.studio.actions.preset.changeLabel': 'Change {field}', + 'engine.studio.actions.preset.runFlow': 'Run a flow', + 'engine.studio.actions.preset.openUrl': 'Open a web page', + 'engine.studio.actions.preset.openPage': 'Open a page in a dialog', + // API view + 'engine.studio.api.subtitle': + 'Auto-generated REST endpoints for this object, derived from its current schema. Copy a request as cURL and call it with your API token.', + 'engine.studio.api.list': 'List records', + 'engine.studio.api.create': 'Create a record', + 'engine.studio.api.get': 'Get one by id', + 'engine.studio.api.update': 'Update a record', + 'engine.studio.api.delete': 'Delete a record', + 'engine.studio.api.copyCurl': 'Copy as cURL', + 'engine.studio.api.copied': 'Copied', + 'engine.studio.api.body': 'Example body', + 'engine.studio.api.bodyOmitted': 'Required fields come first. Optional fields not shown: {fields}', + // Hooks view + 'engine.studio.hooks.renameHeld': 'Not saved yet: a hook keeps the name it was created with. Change the name back to {name} to save your changes.', + 'engine.studio.hooks.none': 'No hooks target this object.', + 'engine.studio.hooks.newButton': 'New hook', + 'engine.studio.hooks.async': 'async', + 'engine.studio.hooks.pick': 'Select a hook to edit it.', + 'engine.studio.hooks.newLabel': 'New hook', + 'engine.studio.designer.group.kind': 'Group', + 'engine.studio.designer.group.settings': 'Group settings', + 'engine.studio.designer.group.nameLabel': 'Group name', + 'engine.studio.designer.group.missing': 'This group no longer exists.', + 'engine.studio.designer.group.collapseLabel': 'Collapse behavior', + 'engine.studio.designer.group.collapseNone': 'Not collapsible', + 'engine.studio.designer.group.collapseExpanded': 'Collapsible · expanded by default', + 'engine.studio.designer.group.collapseCollapsed': 'Collapsible · collapsed by default', + 'engine.studio.designer.group.collapseHint': 'Controls how this group appears in the actual form (see Preview).', + // objectui#11788 — the New automation dialog's trigger choice, left unset. + 'engine.studio.newAutoTrigger.later': 'Choose later on the Start node', + // ── Record Sharing Baseline (OWD) overview (objectui#2505) ────────────── + 'engine.studio.owd.railLabel': 'Record sharing (OWD)', + 'engine.studio.owd.title': 'Record sharing baseline (OWD)', + 'engine.studio.owd.subtitle': 'Object × org-wide default', + 'engine.studio.owd.description': + 'The org-wide default (sharingModel) for every object this package owns — audit the baseline at a glance and batch-edit it. Changes save as per-object drafts and go live through the package Publish, exactly like the per-object Settings tab.', + 'engine.studio.owd.colObject': 'Object', + 'engine.studio.owd.colInternal': 'Sharing model (sharingModel)', + 'engine.studio.owd.none': 'This package declares no objects.', + 'engine.studio.owd.loadFailed': 'Failed to load this package’s objects.', + 'engine.studio.owd.save': 'Save {count} change(s)', + 'engine.studio.owd.saveNone': 'No changes', + 'engine.studio.owd.saved': 'Saved {count} object draft(s).', + 'engine.studio.owd.unsaved': 'Unsaved', + 'engine.studio.owd.byParent': 'By parent → {master}', + 'engine.studio.owd.byParentNoMaster': 'By parent — no master: the object needs exactly one required master-detail field', + 'engine.studio.owd.masterTip': + 'Controlled by parent — record visibility is inherited from the master record.', }; 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 3ed1dcf2575..2d537b860cc 100644 --- a/ui/packages/i18n/src/namespaces/studio/zh.ts +++ b/ui/packages/i18n/src/namespaces/studio/zh.ts @@ -2207,6 +2207,644 @@ const STUDIO_ZH: Record = { 'engine.studio.access.discardConfirm': '放弃「{item}」的待发布草稿吗?已发布的权限集保持不变,只移除未发布的修改。', 'engine.studio.access.discardConfirmUnpublished': '放弃「{item}」吗?它从未发布过,放弃草稿会移除这个权限集。', 'engine.studio.access.discardFailed': '无法放弃草稿', + // The rest of engine.studio.* (all but engine.studio.inspector) and the + // metadata preview panes (engine.*Preview, engine.previewShell; the doc, book, + // flow, page-block and translation previews excepted) (#25922 slice S5). + // objectui#10835 — the dashboard designer's preview canvas (see the en rows). + 'engine.dashboardPreview.empty': '至少添加一个组件才能查看预览。', + 'engine.dashboardPreview.malformed': '某个组件引用了无法解析的对象或字段。', + 'engine.dashboardPreview.loading': '正在加载仪表盘渲染器…', + 'engine.dashboardPreview.selected': '已选中', + 'engine.dashboardPreview.clickToRename': '点击重命名', + 'engine.dashboardPreview.untitled': '未命名', + 'engine.dashboardPreview.saveTitle': '保存标题', + 'engine.dashboardPreview.renameWidget': '重命名组件', + 'engine.dashboardPreview.clearSelection': '取消选择', + 'engine.previewShell.renderFailed': '预览渲染失败', + 'engine.screenPreview.header': '预览', + 'engine.screenPreview.empty': '添加标题、描述、字段或对象表单,即可预览此交互页面。', + 'engine.screenPreview.submit': '提交', + 'engine.screenPreview.noDataSource': '连接后端后即可预览此对象表单。', + 'engine.screenPreview.hiddenOne': '有 {count} 个字段因其“显示条件”而隐藏。', + 'engine.screenPreview.hiddenOther': '有 {count} 个字段因其“显示条件”而隐藏。', + 'engine.screenPreview.defaultTemplate': '“{field}”的默认值模板,按原文显示 —— 运行时填入实际值:', + // objectui#10862 — the flow / automation previews and the CEL try-it dialog (see the en rows). + 'engine.actionPreview.empty': '设置名称和标签,即可查看操作预览。', + 'engine.actionPreview.pill.type': '类型:{type}', + 'engine.actionPreview.pill.object': '对象:{object}', + 'engine.actionPreview.pill.variant': '样式:{variant}', + 'engine.actionPreview.pill.refreshAfter': '执行后刷新', + 'engine.actionPreview.pill.aiOptedOut': 'AI:不开放', + 'engine.actionPreview.pill.aiExposed': 'AI:已开放', + 'engine.actionPreview.locations': '位置:', + 'engine.actionPreview.requires': '需要:', + 'engine.actionPreview.visibleWhen': '可见条件', + 'engine.actionPreview.disabledWhen': '禁用条件', + 'engine.actionPreview.onClick': '点击时', + 'engine.actionPreview.firstAsks': '先询问:', + 'engine.actionPreview.onSuccess': '成功后:', + 'engine.actionPreview.handler.none': '尚未绑定处理程序。', + 'engine.actionPreview.handler.url': '跳转到 {target}', + 'engine.actionPreview.handler.flow': '运行流程 {target}', + 'engine.actionPreview.handler.modal': '打开弹窗 {target}', + 'engine.actionPreview.handler.api': '调用 API 端点 {target}', + 'engine.actionPreview.handler.form': '打开表单视图 {target}(/console/forms/{path})', + 'engine.actionPreview.handler.scriptBody': '运行内联脚本体(L1 表达式或 L2 沙箱 JS)。', + 'engine.actionPreview.handler.scriptNamed': '运行具名脚本 {target}', + 'engine.actionPreview.handler.invoke': '调用 {target}', + 'engine.actionPreview.section.placement': '显示位置', + 'engine.actionPreview.capabilityGateNote': + '不具备 {capabilities} 的用户看不到它 —— 下方的框架展示的是摆放位置,而不是谁能看到它。', + 'engine.actionPreview.section.testRequest': '测试请求', + 'engine.actionPreview.section.inputDialog': '输入对话框', + 'engine.actionPreview.section.resultDialog': '结果对话框', + 'engine.actionPreview.section.scriptBody': '脚本体({language})', + 'engine.actionPreview.dialog.ok': '确定', + 'engine.actionPreview.param.carryOver': '沿用所选行的值(只读)', + 'engine.actionPreview.param.fromRow': '(来自所选行)', + 'engine.actionPreview.param.selectPlaceholder': '选择 {label}', + 'engine.actionPreview.param.recordIdPlaceholder': '{label} 的记录 ID', + 'engine.actionPreview.param.noReference': + '未配置 {reference} 对象,因此记录选择器不可用,对话框会改为要求输入记录 ID。', + 'engine.actionPreview.param.toggle': '开关', + 'engine.actionPreview.param.noChoices': '尚未编写选项 —— 对话框会打开一个空的选择器。', + 'engine.actionPreview.param.searchObject': '搜索 {object}…', + 'engine.actionPreview.param.searchRecords': '搜索记录…', + 'engine.actionPreview.result.title': '结果', + 'engine.actionPreview.result.acknowledge': '我已保存', + 'engine.actionPreview.result.fullJson': '显示完整的 JSON 响应。', + 'engine.actionPreview.placement.sampleRecord': '示例记录', + 'engine.actionPreview.placement.recordDetail': '记录详情', + 'engine.actionPreview.placement.recordBody': '…记录正文…', + 'engine.actionPreview.placement.records': '记录', + 'engine.actionPreview.placement.rows': '第 1 行 · 第 2 行 · 第 3 行', + 'engine.actionPreview.placement.row': '第 {n} 行', + 'engine.actionPreview.placement.section': '分区', + 'engine.actionPreview.placement.sectionBody': '…分区内容…', + 'engine.actionPreview.placement.more': '更多', + 'engine.actionPreview.api.paramsOne': '+ {count} 个由用户在运行时提供的参数', + 'engine.actionPreview.api.paramsOther': '+ {count} 个由用户在运行时提供的参数', + 'engine.actionPreview.api.liveWarning': '会向在线后端发送真实的 {method} 请求 —— 可能修改数据。', + 'engine.actionPreview.api.sending': '发送中…', + 'engine.actionPreview.api.send': '发送测试请求', + 'engine.actionPreview.api.networkError': '网络错误', + 'engine.agentPreview.tryInChat': '在对话中试用', + 'engine.agentPreview.tryInChatTitle': '在新对话中打开此智能体的已保存版本', + 'engine.agentPreview.empty': '在“表单”标签页中填写标签、角色和指令,即可查看智能体预览。', + 'engine.agentPreview.active': '已启用', + 'engine.agentPreview.disabled': '已停用', + 'engine.agentPreview.temperature': '温度 {value}', + 'engine.agentPreview.maxTokens': '上限 {count} token', + 'engine.agentPreview.instructions': '指令', + 'engine.agentPreview.noInstructions': '尚未设置系统提示词。', + 'engine.agentPreview.capabilities': '能力', + 'engine.agentPreview.skills': '技能', + 'engine.agentPreview.noSkills': '未关联技能 —— 此智能体无法使用任何工具。', + 'engine.agentPreview.toolsNote': '工具来自所关联的技能;知识依据在指令中描述。', + 'engine.agentPreview.planning': '规划', + 'engine.agentPreview.memory': '记忆', + 'engine.agentPreview.guardrails': '护栏', + 'engine.agentPreview.permissions': '权限', + 'engine.agentPreview.defaults': '使用默认设置。', + 'engine.agentPreview.noValues': '未设置任何值。', + 'engine.skillPreview.empty': '为技能命名,并至少提供一段指令,即可查看预览。', + 'engine.skillPreview.active': '已启用', + 'engine.skillPreview.disabled': '已停用', + 'engine.skillPreview.model': '模型:{model}', + 'engine.skillPreview.instructions': '指令', + 'engine.skillPreview.noInstructions': '尚无指令 —— 此技能不会向提示词贡献任何内容。', + 'engine.skillPreview.tools': '工具({count})', + 'engine.skillPreview.noTools': '未加入任何白名单工具。', + 'engine.skillPreview.wildcardNote': '通配符在运行时会展开为许多工具 —— 启用前请在智能体中核对匹配到的工具集。', + 'engine.skillPreview.triggerConditions': '触发条件({count})', + 'engine.skillPreview.col.field': '字段', + 'engine.skillPreview.col.operator': '运算符', + 'engine.skillPreview.col.value': '值', + 'engine.skillPreview.allMustHold': '所有条件都必须成立(AND),技能才会激活。', + 'engine.skillPreview.missing': '缺失', + 'engine.skillPreview.missingTitle': 'SkillTriggerConditionSchema 要求此项 —— 此草稿保存时会被拒绝。', + 'engine.toolPreview.openApiConsole': '在 API 控制台中打开', + 'engine.toolPreview.openApiConsoleTitle': '打开 API 控制台以测试调用', + 'engine.toolPreview.empty': '设置名称、描述和参数,即可查看工具预览。', + 'engine.toolPreview.inputParameters': '输入参数', + 'engine.toolPreview.noParameters': '此工具不接受输入参数。', + 'engine.toolPreview.exampleCall': 'LLM 调用示例', + 'engine.toolPreview.outputSchema': '输出结构', + 'engine.toolPreview.col.name': '名称', + 'engine.toolPreview.col.type': '类型', + 'engine.toolPreview.col.description': '描述', + 'engine.toolPreview.required': '必填', + 'engine.toolPreview.enum': '枚举:{values}', + 'engine.toolPreview.default': '默认:', + 'engine.toolPreview.format': '格式:{format}', + 'engine.jobPreview.empty': '设置调度(cron / every / at)和处理程序,即可查看后台任务预览。', + 'engine.jobPreview.active': '已启用', + 'engine.jobPreview.paused': '已暂停', + 'engine.jobPreview.retries': '重试:{count}', + 'engine.jobPreview.retriesBackoff': '重试:{count}(退避 {backoff})', + 'engine.jobPreview.timeout': '超时:{duration}', + 'engine.jobPreview.concurrency': '并发:{count}', + 'engine.jobPreview.schedule': '调度', + 'engine.jobPreview.every': '间隔', + 'engine.jobPreview.at': '时刻', + 'engine.jobPreview.unparseable': '无法解析', + 'engine.jobPreview.noSchedule': '未设置调度 —— 仅在手动触发时运行。', + 'engine.jobPreview.nextRunOne': '接下来 {count} 次运行', + 'engine.jobPreview.nextRunOther': '接下来 {count} 次运行', + 'engine.jobPreview.noFireTime': '无法推算出任何未来的触发时间。请检查调度语法。', + 'engine.jobPreview.noUpcoming': '未来 366 天内没有待运行的计划。', + 'engine.jobPreview.inDelta': '{delta} 后', + 'engine.jobPreview.inNow': '即刻', + 'engine.jobPreview.handler': '处理程序', + 'engine.jobPreview.noHandler': '未绑定处理程序 —— 此任务不会执行任何操作。', + // objectui#10862 (slice 2) — the admin previews and the CEL lint advisories (see the en rows). + 'engine.appPreview.openTitle': '在新标签页中打开此应用', + 'engine.appPreview.open': '打开', + 'engine.appPreview.homeFirst': '首页:打开第一个导航项', + 'engine.appPreview.homeNone': '首页:尚无可生成路由的导航项', + 'engine.appPreview.empty': '没有顶层导航项。在“表单”标签页中添加 {navigation} 条目,即可填充应用的导航。', + 'engine.appPreview.unnamed': '(未命名)', + 'engine.appPreview.noTypeTitle': '每个导航项都需要 `type` 判别字段 —— 此条目将在保存时被拒绝。', + 'engine.appPreview.noType': '无类型', + 'engine.appPreview.areas': '区域', + 'engine.emailTemplatePreview.empty': '在“表单”标签页中填写主题 / 正文,即可查看预览。', + 'engine.emailTemplatePreview.emptyBody': '(正文为空)', + 'engine.emailTemplatePreview.from': '发件人', + 'engine.emailTemplatePreview.to': '收件人', + 'engine.emailTemplatePreview.subject': '主题', + 'engine.emailTemplatePreview.frameTitle': '邮件预览', + 'engine.emailTemplatePreview.variables': '变量', + 'engine.emailTemplatePreview.noVariables': '未找到 {var} 占位符。', + 'engine.emailTemplatePreview.samplePlaceholder': '{name} 的示例值', + 'engine.permissionPreview.empty': '至少授予一项对象、系统或标签页权限,即可查看权限矩阵。', + 'engine.permissionPreview.kind.default': '默认权限集', + 'engine.permissionPreview.kind.set': '权限集', + 'engine.permissionPreview.pill.objects': '{count} 个对象', + 'engine.permissionPreview.pill.systemPerms': '{count} 项系统权限', + 'engine.permissionPreview.pill.tabs': '{count} 个标签页', + 'engine.permissionPreview.pill.rls': '{count} 条 RLS 规则', + 'engine.permissionPreview.section.objects': '对象权限', + 'engine.permissionPreview.section.fields': '字段级覆盖', + 'engine.permissionPreview.section.system': '系统权限', + 'engine.permissionPreview.section.tabs': '标签页可见性', + 'engine.permissionPreview.col.object': '对象', + 'engine.permissionPreview.cap.create': '创建', + 'engine.permissionPreview.cap.read': '读取', + 'engine.permissionPreview.cap.edit': '编辑', + 'engine.permissionPreview.cap.delete': '删除', + 'engine.permissionPreview.cap.export': '导出', + 'engine.permissionPreview.cap.transfer': '转移', + 'engine.permissionPreview.cap.viewAll': '查看全部', + 'engine.permissionPreview.cap.modifyAll': '修改全部', + 'engine.permissionPreview.col.scopeTitle': '读 / 写访问深度(ADR-0057)', + 'engine.permissionPreview.col.scope': '范围', + 'engine.permissionPreview.readScope': '读取范围', + 'engine.permissionPreview.writeScope': '写入范围', + 'engine.permissionPreview.scopeDefault': '默认', + 'engine.permissionPreview.fls.hidden': '隐藏', + 'engine.permissionPreview.fls.editable': '可编辑', + 'engine.permissionPreview.sanityOne': '{count} 项合理性检查未通过', + 'engine.permissionPreview.sanityOther': '{count} 项合理性检查未通过', + 'engine.permissionPreview.warn.editWithoutRead': '授予了编辑却未授予读取(记录更新将失败)。', + 'engine.permissionPreview.warn.deleteWithoutRead': '授予了删除却未授予读取。', + 'engine.permissionPreview.warn.modifyAllWithoutViewAll': '授予了修改全部却未授予查看全部 —— 修改可能作用于不可见的记录。', + 'engine.permissionPreview.warn.writeWiderThanRead': '写入范围({write})比读取范围({read})更宽 —— 编辑可能作用于不可见的记录。', + 'engine.permissionPreview.granted': '已授予', + 'engine.permissionPreview.notGranted': '未授予', + 'engine.permissionPreview.legend.bypass': '绕过共享(查看/修改全部)', + 'engine.permissionPreview.legend.key': 'C R U D = 增删改查 · E T = 导出/转移 · V* M* = 查看/修改全部', + 'engine.positionPreview.empty': '为岗位命名,即可查看预览。', + 'engine.positionPreview.noAccess': '岗位本身不授予数据访问权限。', + 'engine.positionPreview.bind': + '将 {name} 绑定到一个或多个 {permissionSets},以控制 CRUD-VAMA、字段访问和标签页可见性。记录的可见性来自业务单元树和共享规则,而不是岗位。', + 'engine.positionPreview.permissionSets': '权限集', + // objectui#13304 — a position that declares `permissionSets` (zh). + 'engine.positionPreview.grantsVia': '该岗位通过以下{permissionSets}授予访问权限:', + 'engine.positionPreview.visibility': '记录的可见性来自业务单元树和共享规则,而不是岗位。', + // objectui#10202 — the read-only picklist page (zh). + 'engine.picklistPreview.empty': '该选项列表没有名称。', + 'engine.picklistPreview.readOnly': '只读:选项列表归声明它的软件包所有,不能在这里新建、编辑或删除。', + 'engine.picklistPreview.ownedBy': '所属软件包', + 'engine.picklistPreview.options': '选项', + 'engine.picklistPreview.colLabel': '标签', + 'engine.picklistPreview.colValue': '取值', + 'engine.picklistPreview.noOptions': '该选项列表没有声明任何选项。', + 'engine.picklistPreview.extensions': '扩展', + 'engine.picklistPreview.extensionsHint': '其他软件包为该选项列表追加的选项。', + 'engine.picklistPreview.addedBy': '由 {package} 追加', + 'engine.picklistPreview.noExtensions': '没有软件包扩展该选项列表。', + 'engine.picklistPreview.extensionsLoading': '正在加载扩展…', + 'engine.picklistPreview.extensionsFailed': '无法读取已安装的软件包,因此不知道该选项列表有哪些扩展。', + 'engine.objectPreview.renderFailed': '无法渲染表单设计器。请检查“表单”标签页。', + 'engine.datasetPreview.pickObject': '选择一个基础对象', + 'engine.datasetPreview.pickObjectHint': '设置数据集的 `object`,即可用实时数据预览它。', + 'engine.datasetPreview.addMeasure': '添加一个度量', + 'engine.datasetPreview.addMeasureHint': '数据集至少需要一个度量(例如 revenue = sum(amount))才能预览。', + 'engine.datasetPreview.run': '运行预览', + 'engine.datasetPreview.measureOne': '{count} 个度量', + 'engine.datasetPreview.measureOther': '{count} 个度量', + 'engine.datasetPreview.dimensionOne': '{count} 个维度', + 'engine.datasetPreview.dimensionOther': '{count} 个维度', + 'engine.datasetPreview.notInstalled': '未安装分析能力', + 'engine.datasetPreview.notInstalledHint': + '此部署没有分析服务,因此无法运行数据集预览。请安装 @objectstack/service-analytics 并挂载 AnalyticsServicePlugin —— 数据集定义本身没有问题。', + 'engine.datasetPreview.noRows': '没有数据行', + 'engine.datasetPreview.noRowsHint': '数据集在当前范围内没有返回任何数据行。', + 'engine.datasetPreview.chartFailed': '无法为此结果渲染图表 —— 下方表格仍会显示数据。', + 'engine.datasetPreview.ratioAxis': '比率度量({measures})使用右侧坐标轴。', + 'engine.viewPreview.schemaFailed': '该视图的 `type` 可能尚未注册,或缺少必填字段。', + 'engine.viewPreview.noObject': '此视图尚未绑定对象。请在右侧面板中设置绑定的 {object},以获取实时数据和字段选项。', + 'engine.viewPreview.formFailed': '表单视图引用了无法解析的对象或字段。', + 'engine.viewPreview.listFailed': '视图引用了无法解析的对象或字段。', + 'engine.reportPreview.renderFailed': '报表引用了无法解析的数据集/度量,或其配置不完整。', + 'engine.reportPreview.loading': '正在加载报表渲染器…', + 'engine.reportPreview.joinedEmpty': '添加一个区块以预览此联合报表', + 'engine.reportPreview.joinedEmptyHint': '联合报表由绑定数据集的区块堆叠而成。添加一个区块,并在右侧面板中绑定它的数据集 + 度量,即可开始设计。', + 'engine.reportPreview.empty': '绑定一个数据集以预览此报表', + 'engine.reportPreview.emptyHint': '自 9.0 单一形态切换起,报表按维度(rows)分组呈现其数据集的度量(values)。在右侧面板中选择一个数据集即可开始设计。', + 'engine.pagePreview.interfaceFailed': '界面页面引用了一个不可用的来源对象/视图。', + 'engine.pagePreview.schemaFailed': '页面 schema 不完整,或引用了尚未注册的组件。', + 'engine.pagePreview.previewRecord': '预览记录', + 'engine.pagePreview.sampleOne': '{count} 条示例', + 'engine.pagePreview.sampleOther': '{count} 条示例', + 'engine.pagePreview.recordFallback': '(记录)', + 'engine.pagePreview.addComponents': '向页面添加组件即可查看预览。', + 'engine.pagePreview.blockN': '区块 {n}', + 'engine.validationPreview.empty': '为校验规则命名即可查看预览。', + 'engine.validationPreview.active': '已启用', + 'engine.validationPreview.disabled': '已停用', + 'engine.validationPreview.priority': '优先级 {priority}', + 'engine.validationPreview.on': '触发于 {events}', + 'engine.validationPreview.noMessage': '未设置消息', + 'engine.validationPreview.tags': '标签', + 'engine.validationPreview.removed.unique': + '唯一性是唯一索引(INDEX),不是校验规则:请在对象上声明为 `indexes: [{ fields: [...], unique: \'organization\' }]`(每个组织一个持有者),或用 `unique: \'global\'` 表示整个安装唯一(或在字段上设置 `unique: true`)。先 SELECT 再 INSERT 的规则天生存在竞态(TOCTOU);数据库约束则不会。', + 'engine.validationPreview.removed.async': + '远程/异步校验属于表单层的职责 —— 放在服务端写入路径上会带来 SSRF 与延迟风险。请把它留在表单中,或用唯一索引或生命周期钩子来强制其背后的约束。', + 'engine.validationPreview.removed.custom': + '任意校验代码应放在 `beforeInsert` / `beforeUpdate` 生命周期钩子中 —— 那是有类型、受支持的扩展点。', + 'engine.validationPreview.notARule': '不是校验规则', + 'engine.validationPreview.removedType': '{type} 已从 ValidationRuleSchema 中移除 —— 此规则无法保存。', + 'engine.validationPreview.condition': '条件', + 'engine.validationPreview.when': '条件成立时', + 'engine.validationPreview.thenApply': '则应用', + 'engine.validationPreview.noNested': '未设置嵌套规则 —— 此规则不强制任何约束。', + 'engine.validationPreview.otherwiseApply': '否则应用', + 'engine.validationPreview.transitions': '状态流转', + 'engine.validationPreview.transitionsOn': '{field} 的状态流转', + 'engine.validationPreview.noTransitions': '未声明任何状态流转。', + 'engine.validationPreview.deadEnd': '∅(无后续状态)', + 'engine.validationPreview.createdIn': '可在以下状态创建', + 'engine.validationPreview.format': '格式', + 'engine.validationPreview.formatOn': '{field} 的格式', + 'engine.validationPreview.builtIn': '内置:', + 'engine.validationPreview.regex': '正则:', + 'engine.validationPreview.noFormat': '未设置格式或正则表达式。', + 'engine.validationPreview.fieldsInvolved': '涉及字段', + 'engine.validationPreview.none': '无', + 'engine.validationPreview.crossFieldCondition': '跨字段条件', + 'engine.validationPreview.jsonSchemaOn': '{field} 的 JSON Schema', + 'engine.validationPreview.noJsonSchema': '未设置 JSON Schema。', + 'engine.validationPreview.rule': '规则', + 'engine.validationPreview.unknownType': '未知的规则类型“{type}”。仅显示通用字段。', + 'engine.validationPreview.noType': '无类型', + 'engine.validationPreview.noExpression': '未设置表达式。', + // ── StudioDesignSurface (ADR-0080 WYSIWYG design surface) ────────────── + // Shared chrome + 'engine.studio.cancel': '取消', + 'engine.studio.creating': '创建中…', + 'engine.studio.createItem.savedAs': '保存为', + 'engine.studio.createItem.notDerived': '显示名称里没有可用来生成标识的英文字母,请手动填写英文标识(小写字母、数字和下划线)。', + // `383502b23` — see the English entry. This value must share no leading + // run with `engine.studio.app.create`, which opens the same dialog. + 'engine.studio.createDraft': '存为草稿', + 'engine.studio.saveDraft': '保存草稿', + 'engine.studio.autoSaving': '保存中…', + // Standard create-dialog field labels (shared by object / app / flow / permission). + 'engine.studio.app.nameLabel': '应用名称', + 'engine.studio.app.idLabel': '标识', + 'engine.studio.pkg.manage': '软件包信息与设置', + 'engine.studio.pkg.manageMissing': '软件包 {id} 不在已安装列表中 —— 可能已在别处被删除或卸载。', + 'engine.studio.pkg.manageRefreshFailed': + '软件包 {id} 在该操作后未能刷新 —— 已关闭面板,以免把操作前的记录当作最新呈现。{error}', + 'engine.studio.publish': '发布', + 'engine.studio.loading': '加载中…', + 'engine.studio.loadFailed': '加载失败', + 'engine.studio.refusal.choiceWithoutOptions': + '更改未保存:字段「{field}」至少需要一个选项。请添加选项,或将该字段改为非选项类型。', + 'engine.studio.refusal.relationshipWithoutTarget': + '更改未保存:字段「{field}」未指定要关联的对象。请选择对象,或将该字段改为非关联类型。', + 'engine.studio.refusal.issue': '更改未保存。请检查{where} —— {problem}', + 'engine.studio.refusal.more': '(另有 {count} 处,见详情)', + 'engine.studio.refusal.unlocated': '更改未保存:服务端拒绝了此草稿。详情中说明了需要修改的内容。', + 'engine.studio.refusal.field': '字段「{field}」', + 'engine.studio.refusal.step': '步骤「{step}」', + 'engine.studio.refusal.stepInput': '步骤「{step}」的「{input}」', + 'engine.studio.refusal.navItem': '导航项「{item}」', + 'engine.studio.refusal.navItemInput': '导航项「{item}」的「{input}」', + 'engine.studio.refusal.details': '详情', + 'engine.studio.refusal.show': '定位', + 'engine.studio.held.needsOptions': '字段「{field}」至少需要一个选项', + 'engine.studio.held.needsTarget': '字段「{field}」需要指定要关联的对象', + 'engine.studio.held.needsInput': '步骤「{step}」的「{input}」需要填写', + 'engine.studio.held.needsCondition': '规则「{rule}」需要一个条件', + 'engine.studio.held.needsThenCondition': '规则「{rule}」的「Then」规则需要一个条件', + 'engine.studio.held.needsRuleField': '规则「{rule}」需要选择一个字段', + 'engine.studio.held.needsRuleMessage': '规则「{rule}」需要一条错误消息', + 'engine.studio.held.widgetNeedsInput': '组件「{widget}」的「{input}」需要填写', + 'engine.studio.held.line': '尚未保存:{clause}。更改已保留在此处,补全后会自动保存。', + 'engine.studio.held.publish': '未发布:{clause},该更改尚未保存。请先补全,再发布。', + 'engine.studio.held.inputHint': '必填。填写后才会保存更改。', + 'engine.studio.unpublishedDraft': '未发布草稿', + 'engine.studio.itemDraftPending': '草稿 · {count} 项未发布', + 'engine.studio.itemDraftPendingTitle': '此项有未发布的草稿。本包共有 {count} 项未发布的更改,发布时一起生效。', + 'engine.studio.unpublished': '未发布', + 'engine.studio.new': '新建', + 'engine.studio.edit': '编辑', + 'engine.studio.done': '完成', + 'engine.studio.close': '关闭', + 'engine.studio.deselect': '取消选择', + 'engine.studio.toggleRail': '切换侧栏', + 'engine.studio.landingHome': 'Studio 首页', + 'engine.studio.desktopHint': 'Studio 专为桌面屏幕设计。', + 'engine.studio.aiCopilot': 'AI 副驾', + // objectui#12725 — Studio 右栏:属性或 AI 副驾二选一。 + 'engine.studio.slot.label': '右栏', + 'engine.studio.slot.switchHint': '⌘.(Ctrl+.)切换', + 'engine.studio.slot.collapse': '收起右栏', + 'engine.studio.slot.expand': '展开右栏', + 'engine.studio.slot.backToChat': '回到对话', + 'engine.studio.slot.planPending': '方案待确认', + 'engine.studio.slot.openFullPage': '在完整页面中打开', + 'engine.studio.pillar.data': '数据', + 'engine.studio.pillar.automations': '自动化', + 'engine.studio.pillar.interfaces': '界面', + 'engine.studio.pillar.access': '权限', + // Header · package-level publish + app bridge + 'engine.studio.changes': '变更', + 'engine.studio.publishTitle': '一次性确认并发布全部待发布草稿(整包 · 一次原子发布)', + 'engine.studio.publishNoneTitle': '没有待发布的草稿', + 'engine.studio.publishedAll': '已发布本软件包的全部草稿(一次原子发布)', + // objectui#11807 — the publish toast names how many items went live. + 'engine.studio.publishedAllCount': '已发布本软件包的 {count} 项(一次原子发布)', + 'engine.studio.publishedAllCountOne': '已发布本软件包的 {count} 项(一次原子发布)', + // objectui#12929 — the batch door's backfill receipt, one clause per field + // the review sheet named: the rows the server wrote, and why a field stopped. + 'engine.studio.backfillWritten': '已为 {field} 补默认值:{count} 条记录', + 'engine.studio.backfillStopped': '{field} 的默认值补写中途停止(停止前已写入 {count} 条):{error}', + 'engine.studio.app.open': '打开应用', + 'engine.studio.app.openTitle': '打开应用「{label}」(发布后的前端界面)', + 'engine.studio.app.create': '创建应用', + 'engine.studio.app.noneTitle': '这个软件包还没有应用(前端界面)— 创建一个', + 'engine.studio.app.willOpenAfterPublish': '发布后这里会变成「打开应用」', + 'engine.studio.app.pending': '应用「{label}」待发布', + 'engine.studio.app.namePlaceholder': '应用名称(如:订单中心)', + 'engine.studio.app.idPlaceholder': '标识符(如:orders_app)', + 'engine.studio.app.savedDraft': '应用「{label}」已存为草稿 — 发布后即可打开', + // Package switcher + 'engine.studio.pkg.switchTitle': '切换 / 新建软件包', + 'engine.studio.pkg.readonly': '只读', + 'engine.studio.pkg.readonlyHint': '只读软件包 — 请切换或新建可写软件包后再编辑。', + 'engine.studio.pkg.readonlyLink': '只读 · 用覆盖定制', + 'engine.studio.app.scaffoldNav': '将本包的对象添加为菜单项', + 'engine.studio.landing.title': '应用构建', + 'engine.studio.landing.description': '在一个可写软件包里设计对象、表单、自动化与界面;编辑存为草稿,整包一次发布。源码与已安装的软件包为只读:可在此浏览,并用环境覆盖进行定制。', + 'engine.studio.landing.mineHeading': '我的软件包(可写)', + 'engine.studio.landing.noneWritable': '还没有可写软件包 — 新建一个开始。', + 'engine.studio.landing.dup': '复制', + 'engine.studio.landing.dupTitle': '复制这个软件包为新的可写副本(类似「复制基座」)', + 'engine.studio.landing.dupDefaultName': '{name} 副本', + 'engine.studio.landing.dupNamePlaceholder': '副本名称', + 'engine.studio.landing.dupIdPlaceholder': '副本包 ID', + 'engine.studio.landing.dupGo': '复制并进入构建器', + 'engine.studio.landing.dupCreated': '已复制为可写软件包「{name}」', + 'engine.studio.landing.installedHeading': '已安装(只读 · 可浏览)', + // objectui#11808 — the routes a read-only package does have. + 'engine.studio.landing.readonlyHint': '源码与已安装的软件包为只读,不能在此编辑或复制。对于接受环境覆盖的元数据类型,可用环境覆盖(overlay)进行定制。', + 'engine.studio.landing.overlay': '在设置中打开', + 'engine.studio.landing.overlayTitle': '打开该软件包的元数据:每种类型都会标明是否接受环境覆盖', + 'engine.studio.landing.marketplace': '或从应用市场安装一个模板应用', + // objectui#11553 — the package-less scope: the organization's own flows. + 'engine.studio.landing.orgHeading': '不属于软件包', + 'engine.studio.org.name': '组织流程', + 'engine.studio.org.hint': '不属于任何软件包的流程', + 'engine.studio.org.description': '本组织自有、不属于任何软件包的流程,例如软件包流程的克隆副本。可直接编辑。', + 'engine.studio.org.none': '还没有不属于软件包的流程。在 设置 › 打包自动化 中克隆一个软件包流程,即可在这里编辑副本。', + 'engine.studio.org.openSetup': '在设置中打开', + 'engine.studio.publishedAllFlows': '已发布 {count} 个不属于软件包的流程草稿', + 'engine.studio.publishedAllFlowsOne': '已发布 {count} 个不属于软件包的流程草稿', + 'engine.studio.designer.search': '搜索…', + 'engine.studio.designer.select': '请选择…', + 'engine.studio.designer.pickDate': '选择日期…', + 'engine.studio.designer.fieldAria': '{label} — 点选改属性,拖动排序', + 'engine.studio.designer.fieldAriaReadOnly': '{label} — 点选查看属性', + 'engine.studio.designer.fieldRole': '可拖动字段', + 'engine.studio.designer.groupUp': '上移分组', + 'engine.studio.designer.groupDown': '下移分组', + 'engine.studio.designer.groupDelete': '删除分组', + 'engine.studio.designer.groupDeleteTitle': '删除分组(字段归为未分组)', + 'engine.studio.designer.dropHere': '拖字段到这里', + 'engine.studio.designer.ungrouped': '未分组', + 'engine.studio.designer.newGroup': '新分组', + 'engine.studio.designer.fieldTitleReadOnly': '点选字段查看属性', + 'engine.studio.designer.toolbarHint': '拖动字段排布 · 点选字段编辑属性', + 'engine.studio.designer.addGroup': '添加分组', + 'engine.studio.formDnd.instructions': + '按空格键或回车键拿起字段。拖动时用方向键移动,再按空格键或回车键放到新位置,按 Esc 键取消。', + 'engine.studio.formDnd.start': '已拿起 {field},当前在 {group},第 {position} 个,共 {total} 个。', + 'engine.studio.formDnd.over': '{field} 正移到 {group},第 {position} 个,共 {total} 个。', + 'engine.studio.formDnd.overNone': '{field} 不在任何分组上。', + 'engine.studio.formDnd.end': '{field} 已移到 {group},第 {position} 个,共 {total} 个。', + 'engine.studio.formDnd.endNone': '{field} 没有放到分组里,已回到 {group},第 {position} 个,共 {total} 个。', + 'engine.studio.formDnd.cancel': '已取消拖动。{field} 已回到 {group},第 {position} 个,共 {total} 个。', + 'engine.studio.rules.title': '验证规则', + 'engine.studio.rules.none': '还没有验证规则。', + 'engine.studio.rules.newButton': '新建验证规则', + 'engine.studio.rules.noMessage': '(无消息)', + 'engine.studio.rules.notSaved': '未保存 —— 需要条件', + 'engine.studio.rules.notSavedField': '未保存 —— 需要字段', + 'engine.studio.rules.notSavedMessage': '未保存 —— 需要消息', + 'engine.studio.rules.structured': '「{type}」类型的规则带有结构化配置(状态机转移表 / 格式约束等),暂不支持在此编辑 —— 请在代码包中维护。消息:{message}', + 'engine.studio.rules.none2': '(无)', + 'engine.studio.rules.deleteRule': '删除规则', + 'engine.studio.rules.nameLabel': '规则名(snake_case,唯一)', + 'engine.studio.rules.messageLabel': '错误消息(校验失败时展示给用户)', + 'engine.studio.rules.messagePlaceholder': '例如:完成日期在状态为已完成时必填', + 'engine.studio.rules.celPre': '失败条件(CEL)—— 条件为 ', + 'engine.studio.rules.celTrue': '真', + 'engine.studio.rules.celMid': ' 时,拒绝保存并显示上面的消息。', + 'engine.studio.rules.whenPre': '前提条件(CEL)—— 条件为 ', + 'engine.studio.rules.whenMid': ' 时,应用下面的「Then」规则;否则应用「Otherwise」规则(如已设置)。', + 'engine.studio.rules.severity': '严重度', + 'engine.studio.rules.severityError': 'error(拒绝保存)', + 'engine.studio.rules.enabled': '启用', + 'engine.studio.rules.delete': '删除', + 'engine.studio.rules.type': '类型', + 'engine.studio.rules.newType': '新建验证类型', + 'engine.studio.rules.typeScript': 'Script —— CEL 失败条件', + 'engine.studio.rules.typeCrossField': 'Cross-field —— 跨字段 CEL', + 'engine.studio.rules.typeStateMachine': 'State machine —— 允许的状态流转', + 'engine.studio.rules.typeFormat': 'Format —— 正则 / 内置格式', + 'engine.studio.rules.typeJsonSchema': 'JSON schema —— 校验 JSON 字段', + 'engine.studio.rules.typeConditional': 'Conditional —— 条件成立时应用规则', + 'engine.studio.rules.label': '标签(可选)', + 'engine.studio.rules.field': '字段', + 'engine.studio.rules.statusField': '状态字段', + 'engine.studio.rules.fields': '参与字段', + 'engine.studio.rules.noFields': '该对象暂无字段。', + 'engine.studio.rules.pickField': '—— 选择字段 ——', + 'engine.studio.rules.format': '内置格式', + 'engine.studio.rules.formatNone': '—— 无 ——', + 'engine.studio.rules.regex': '正则表达式', + 'engine.studio.rules.transitions': '允许的状态流转(从 → 下一状态)', + 'engine.studio.rules.transitionFrom': '起始状态', + 'engine.studio.rules.transitionTo': '下一状态(逗号分隔)', + 'engine.studio.rules.addTransition': '新增流转', + 'engine.studio.rules.jsonSchema': 'JSON Schema', + 'engine.studio.rules.invalidJson': 'JSON 格式无效', + 'engine.studio.rules.then': 'Then —— 条件成立时应用的规则(JSON)', + 'engine.studio.rules.otherwise': 'Otherwise —— 条件不成立时应用的规则(JSON,可选)', + 'engine.studio.rules.events': '触发时机', + 'engine.studio.rules.event.insert': '新建', + 'engine.studio.rules.event.update': '更新', + 'engine.studio.rules.priority': '优先级', + 'engine.studio.rules.presets': '常用规则', + 'engine.studio.rules.advanced': '高级', + 'engine.studio.rules.preset.endAfterStart': '结束日期不早于开始日期', + 'engine.studio.rules.preset.endAfterStartNeeds': '需要该对象有两个日期字段。', + 'engine.studio.rules.preset.endAfterStartMessage': '{end}不能早于{start}。', + 'engine.studio.rules.preset.notNegative': '数值不能为负', + 'engine.studio.rules.preset.notNegativeNeeds': '需要该对象有一个数值字段。', + 'engine.studio.rules.preset.notNegativeMessage': '{field}不能为负数。', + 'engine.studio.rules.preset.rejectWhen': '满足条件时拒绝保存…', + 'engine.studio.rules.preset.rejectWhenHint': '自行搭建条件,无需写代码。', + 'engine.studio.rules.preset.usesOne': '使用「{field}」。', + 'engine.studio.rules.preset.usesTwo': '使用「{start}」和「{end}」。', + 'engine.studio.pkg.writable': '可写', + 'engine.studio.pkg.heading': '软件包(应用)', + 'engine.studio.pkg.none': '暂无应用软件包', + 'engine.studio.pkg.createWritable': '创建可写软件包', + 'engine.studio.pkg.new': '新建软件包(可写基座)', + 'engine.studio.pkg.created': '软件包 {name} 已创建(可写)', + // Nav item inspector (Interfaces pillar) + 'engine.studio.nav.selectItem': '在左侧选择一个菜单项。', + 'engine.studio.nav.label': '标签', + 'engine.studio.nav.linkObject': '链接到对象', + 'engine.studio.nav.chooseObject': '— 选择对象 —', + 'engine.studio.nav.boundHint': '这个菜单项会打开该对象的记录列表。', + 'engine.studio.nav.unboundHint': '选择一个对象,菜单项将打开它的记录列表。', + 'engine.studio.nav.noObjects': '这个软件包还没有对象 — 请先在「数据」中创建一个。', + 'engine.studio.nav.eyebrow': '导航项 · {kind}', + 'engine.studio.nav.icon': '图标', + 'engine.studio.nav.visibleWhen': '可见条件', + 'engine.studio.draftMark.chip': '草稿', + 'engine.studio.draftMark.sourceLabel': '来源', + 'engine.studio.draftMark.source': '由 AI 副驾在 {when}作为草稿加入', + 'engine.studio.draftMark.sourceJustNow': '由 AI 副驾刚刚作为草稿加入', + 'engine.studio.draftMark.sourceItems': '{items} {count} 项由 AI 副驾在 {when}作为草稿加入', + 'engine.studio.draftMark.sourceItemsJustNow': '{items} {count} 项由 AI 副驾刚刚作为草稿加入', + 'engine.studio.draftMark.item': '「{label}」', + 'engine.studio.draftMark.minutesAgo': '{n} 分钟前', + 'engine.studio.draftMark.hoursAgo': '{n} 小时前', + 'engine.studio.draftMark.daysAgo': '{n} 天前', + // objectui#11823 (step 2) — the Interfaces pillar's New dashboard / New report. + 'engine.studio.interfaces.create.menuTitle': '在本包中新建仪表盘、报表或页面', + 'engine.studio.interfaces.create.dashboard': '新建仪表盘', + 'engine.studio.interfaces.create.report': '新建报表', + 'engine.studio.interfaces.create.description': '存为本包的草稿,加入 {app} 的导航,并在画布上打开。', + 'engine.studio.interfaces.create.dashboardNameLabel': '仪表盘名称', + 'engine.studio.interfaces.create.reportNameLabel': '报表名称', + 'engine.studio.interfaces.create.dashboardNamePlaceholder': '仪表盘名称(如:销售概览)', + 'engine.studio.interfaces.create.reportNamePlaceholder': '报表名称(如:各区域收入)', + 'engine.studio.interfaces.create.idLabel': '标识', + 'engine.studio.interfaces.create.dashboardIdPlaceholder': '标识符(如:sales_overview)', + 'engine.studio.interfaces.create.reportIdPlaceholder': '标识符(如:revenue_by_region)', + 'engine.studio.interfaces.create.dashboardTaken': '已有标识为「{name}」的仪表盘,请换一个标识。', + 'engine.studio.interfaces.create.reportTaken': '已有标识为「{name}」的报表,请换一个标识。', + 'engine.studio.interfaces.create.dataset': '数据集', + 'engine.studio.interfaces.create.datasetPlaceholder': '选择数据集…', + 'engine.studio.interfaces.create.measure': '度量', + 'engine.studio.interfaces.create.measurePlaceholder': '选择度量…', + 'engine.studio.interfaces.create.reportHint': '报表展示一个数据集的度量。创建后可在属性面板中添加更多度量与分组。', + 'engine.studio.interfaces.create.noDatasets': '还没有可用于报表的数据集。报表展示的是数据集的度量,请先创建数据集。', + 'engine.studio.interfaces.create.datasetsFailed': '无法加载数据集:{error}', + 'engine.studio.interfaces.create.noMeasures': '这个数据集没有声明度量,报表无法展示它。', + 'engine.studio.interfaces.create.needsBinding': '请选择这个报表读取的数据集,以及其中的一个度量。', + 'engine.studio.interfaces.create.page': '新建页面', + 'engine.studio.interfaces.create.pageNameLabel': '页面名称', + 'engine.studio.interfaces.create.pageNamePlaceholder': '页面名称(如:团队手册)', + 'engine.studio.interfaces.create.pageIdPlaceholder': '标识符(如:team_handbook)', + 'engine.studio.interfaces.create.pageTaken': '已有标识为「{name}」的页面,请换一个标识。', + 'engine.studio.interfaces.create.pageKind': '编写方式', + 'engine.studio.interfaces.create.pageKindHtml': 'HTML', + 'engine.studio.interfaces.create.pageKindReact': 'React', + 'engine.studio.interfaces.create.pageKindHtmlHint': '由平台组件与 HTML 标签组成,只解析、不执行。页面打开时,源码与实时预览并排显示。', + 'engine.studio.interfaces.create.pageKindReactHint': '真正的 React 代码,在页面渲染时运行,可使用状态、副作用与事件处理。页面打开时,源码与实时预览并排显示。', + 'engine.studio.interfaces.create.pageStarter': '新页面。请在源码编辑器中编写它的内容。', + // Actions view + 'engine.studio.actions.none': '该对象没有操作。', + 'engine.studio.actions.newButton': '新建操作', + 'engine.studio.actions.editorMissing': '本次会话没有注册操作编辑器,因此无法在这里编辑该操作的属性。', + 'engine.studio.actions.newLabel': '新操作', + 'engine.studio.actions.delete': '删除', + 'engine.studio.actions.presets': '常用操作', + 'engine.studio.actions.notSaved': '未保存 —— 需要「{input}」', + 'engine.studio.actions.heldNeeds': '操作「{action}」需要「{input}」', + 'engine.studio.actions.preset.change': '修改下拉选择字段', + 'engine.studio.actions.preset.changeHint': '询问新的「{field}」,然后保存。', + 'engine.studio.actions.preset.changeNeeds': '需要该对象有一个下拉选择字段。', + 'engine.studio.actions.preset.changeLabel': '修改{field}', + 'engine.studio.actions.preset.runFlow': '运行流程', + 'engine.studio.actions.preset.openUrl': '打开网页', + 'engine.studio.actions.preset.openPage': '在对话框中打开页面', + // API view + 'engine.studio.api.subtitle': + '该对象自动生成的 REST 端点,依据当前 schema 派生。可复制为 cURL,带上你的 API 令牌调用。', + 'engine.studio.api.list': '列出记录', + 'engine.studio.api.create': '创建记录', + 'engine.studio.api.get': '按 id 获取单条', + 'engine.studio.api.update': '更新记录', + 'engine.studio.api.delete': '删除记录', + 'engine.studio.api.copyCurl': '复制为 cURL', + 'engine.studio.api.copied': '已复制', + 'engine.studio.api.body': '示例请求体', + 'engine.studio.api.bodyOmitted': '必填字段排在最前。未列出的可选字段:{fields}', + // Hooks view + 'engine.studio.hooks.renameHeld': '尚未保存:钩子会保留创建时的名称。把名称改回 {name} 即可保存更改。', + 'engine.studio.hooks.none': '没有钩子作用于该对象。', + 'engine.studio.hooks.newButton': '新建钩子', + 'engine.studio.hooks.async': '异步', + 'engine.studio.hooks.pick': '选择一个钩子进行编辑。', + 'engine.studio.hooks.newLabel': '新钩子', + 'engine.studio.designer.group.kind': '分组', + 'engine.studio.designer.group.settings': '分组设置', + 'engine.studio.designer.group.nameLabel': '分组名称', + 'engine.studio.designer.group.missing': '该分组已不存在。', + 'engine.studio.designer.group.collapseLabel': '折叠方式', + 'engine.studio.designer.group.collapseNone': '不可折叠', + 'engine.studio.designer.group.collapseExpanded': '可折叠 · 默认展开', + 'engine.studio.designer.group.collapseCollapsed': '可折叠 · 默认折叠', + 'engine.studio.designer.group.collapseHint': '控制该分组在真实表单(预览)中的折叠方式。', + // objectui#11788 — 新建自动化对话框里不选触发方式时的选项。 + 'engine.studio.newAutoTrigger.later': '稍后在开始节点上选择', + // ── 记录共享基线(OWD)总览 (objectui#2505) ────────────────────────── + 'engine.studio.owd.railLabel': '记录共享(OWD)', + 'engine.studio.owd.title': '记录共享基线(OWD)', + 'engine.studio.owd.subtitle': '对象 × 组织级默认', + 'engine.studio.owd.description': + '本包每个对象的组织级默认(sharingModel)—— 一屏审计基线并批量修改。改动以“逐对象草稿”保存,随包发布上线,与对象“设置”页完全一致。', + 'engine.studio.owd.colObject': '对象', + 'engine.studio.owd.colInternal': '共享模型(sharingModel)', + 'engine.studio.owd.none': '本包未声明任何对象。', + 'engine.studio.owd.loadFailed': '加载本包对象失败。', + 'engine.studio.owd.save': '保存 {count} 处改动', + 'engine.studio.owd.saveNone': '无改动', + 'engine.studio.owd.saved': '已保存 {count} 个对象草稿。', + 'engine.studio.owd.unsaved': '未保存', + 'engine.studio.owd.byParent': '受父级控制 → {master}', + 'engine.studio.owd.byParentNoMaster': '受父级控制 —— 没有主记录:该对象需要恰好一个必填的主从字段', + 'engine.studio.owd.masterTip': + '受父级控制 —— 记录可见性继承自主记录。', }; export default STUDIO_ZH;