From 9a1fbafcd62fbfb45dbb49f1438ba17f54a0d131 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 20:08:33 +0000 Subject: [PATCH 1/2] feat(ui): five Studio pillars, the data source pages and the field types move into the studio namespace (slice S4) The engine.studio.{access,if,data,auto,settings} rows, the data source pages' engine.datasource / engine.externalDatasource / engine.datasourcePreview rows and the field types' engine.fieldType / engine.fieldTypeDesc / engine.fieldCategory rows (549 keys in en and in zh) leave app-shell's designer table for @object-ui/i18n's lazy studio namespace, text unchanged. lookupString already asks the table first and the namespace second, so no call site changes. Claude-Session: https://claude.ai/code/session_01JQwxbX9LnJhjTQPJEmoBYi Co-authored-by: Claude --- .../src/views/metadata-admin/i18n.ts | 1247 ----------------- ui/packages/i18n/src/namespaces/studio/en.ts | 668 +++++++++ ui/packages/i18n/src/namespaces/studio/zh.ts | 587 ++++++++ 3 files changed, 1255 insertions(+), 1247 deletions(-) diff --git a/ui/packages/app-shell/src/views/metadata-admin/i18n.ts b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts index e3a19eb3467..bc980f68266 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -331,69 +331,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.sharingRule.clone.sourceOff': 'The declared rule "{source}" is switched off.', 'engine.sharingRule.clone.openCopy': 'Open the new rule', 'engine.sharingRule.clone.done': 'Done', - // objectui#13333 — the datasource list's delete confirm and the type-level - // action confirm, both in-app dialogs naming what they act on. - 'engine.datasource.deleteTitle': 'Delete datasource "{item}"?', - 'engine.datasource.deleteBody': 'The connection {name} is removed. This cannot be undone.', - 'engine.datasource.delete': 'Delete', - // objectui#13460 — the datasource page's own copy. Author data (names, driver - // labels and descriptions, server messages) and the driver config schema's enum - // option values (objectui#13353) are not rows here. - 'engine.datasource.title': 'Datasources', - 'engine.datasource.subtitle': 'Connect external databases and sync their tables in as objects.', - 'engine.datasource.refresh': 'Refresh', - 'engine.datasource.new': 'New datasource', - 'engine.datasource.loading': 'Loading…', - 'engine.datasource.empty': 'No datasources yet. Click {action} to connect one.', - 'engine.datasource.test': 'Test', - 'engine.datasource.sync': 'Sync objects', - 'engine.datasource.edit': 'Edit', - 'engine.datasource.statusOk': 'connected', - 'engine.datasource.statusError': 'connect failed', - 'engine.datasource.statusBlocked': 'blocked by policy', - 'engine.datasource.statusUnvalidated': 'not connected', - 'engine.datasource.editTitle': 'Edit “{name}”', - 'engine.datasource.editDescription': 'Update the connection. Leave the credential blank to keep the current one.', - 'engine.datasource.newDescription': 'Connect an external database. Credentials are encrypted and never stored in metadata.', - 'engine.datasource.name': 'Name', - 'engine.datasource.nameInvalid': 'Lowercase letters, digits, and underscores; must not start with a digit.', - 'engine.datasource.label': 'Label', - 'engine.datasource.labelPlaceholder': 'Human-friendly name', - 'engine.datasource.driver': 'Driver', - 'engine.datasource.driverPlaceholder': 'Choose a driver', - 'engine.datasource.schemaMode': 'Schema mode', - 'engine.datasource.schemaModeManaged': 'Managed (ObjectStack owns the schema)', - 'engine.datasource.schemaModeExternal': 'External (read existing schema as-is)', - 'engine.datasource.schemaModeValidateOnly': 'Validate only', - 'engine.datasource.testConnection': 'Test connection', - 'engine.datasource.connectionFailed': 'Connection failed', - 'engine.datasource.connectionOk': 'Connection ok', - 'engine.datasource.connectionOkLatency': 'Connection ok ({latency}ms)', - 'engine.datasource.saveChanges': 'Save changes', - 'engine.datasource.create': 'Create datasource', - 'engine.datasource.syncTitle': 'Sync objects from “{name}”', - 'engine.datasource.syncDescription': 'Pick the remote tables to import as objects. Each becomes an object definition.', - 'engine.datasource.introspecting': 'Introspecting…', - 'engine.datasource.noRemoteTables': 'No remote tables found on this datasource.', - 'engine.datasource.columnCount': '{count} cols', - 'engine.datasource.createObjectsNone': 'Create objects', - 'engine.datasource.createObjectsOne': 'Create 1 object', - 'engine.datasource.createObjects': 'Create {count} objects', - 'engine.datasource.failed': 'failed', - 'engine.datasource.rowTestOk': '{name}: connection ok', - 'engine.datasource.rowTestOkLatency': '{name}: connection ok ({latency}ms)', - 'engine.datasource.loadFailed': 'Load datasources: {error}', - 'engine.datasource.loadDriversFailed': 'Load drivers: {error}', - 'engine.datasource.openFailed': 'Open {name}: {error}', - 'engine.datasource.deleted': 'Deleted {name}.', - 'engine.datasource.deleteFailed': 'Delete {name}: {error}', - 'engine.datasource.created': 'Created {name}.', - 'engine.datasource.updated': 'Updated {name}.', - 'engine.datasource.saveFailed': 'Save: {error}', - 'engine.datasource.introspectFailed': 'Introspect {name}: {error}', - 'engine.datasource.syncedOne': 'Synced 1 object from {name}.', - 'engine.datasource.synced': 'Synced {count} objects from {name}.', - 'engine.datasource.syncFailed': 'Failed: {failures}', 'engine.typeAction.confirmTitle': '{action} on "{item}"?', 'engine.typeAction.confirmTitleNoItem': '{action}?', 'engine.layers.code': 'Code', @@ -851,16 +788,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.datasourcePreview.empty': 'Set a name and at least a driver to see the datasource preview.', - 'engine.datasourcePreview.unknownDriver': 'unknown', - 'engine.datasourcePreview.active': 'Active', - 'engine.datasourcePreview.disabled': 'Disabled', - 'engine.datasourcePreview.connection': 'Connection', - 'engine.datasourcePreview.noConfig': 'No config keys set.', - 'engine.datasourcePreview.redacted': 'redacted', - 'engine.datasourcePreview.pool': 'Pool', - 'engine.datasourcePreview.notConfigured': 'not configured', - 'engine.datasourcePreview.keyCount': '{{count} keys}', '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', @@ -1129,69 +1056,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.fieldsListEditor.empty': 'No columns yet. Add a field below.', 'engine.fieldsListEditor.colN': 'col {n}', 'engine.viewColumnPanes.remove': 'Remove {label}', - 'engine.externalDatasource.unavailable': 'Federation is not enabled on this server.', - 'engine.externalDatasource.saveFirst': - 'Save the datasource first to browse its remote tables and validate federated objects.', - 'engine.externalDatasource.title': 'External Datasource', - 'engine.externalDatasource.writesAllowed': 'writes allowed', - 'engine.externalDatasource.readOnly': 'read-only', - 'engine.externalDatasource.snapshot': 'snapshot {time}', - 'engine.externalDatasource.refresh': 'Refresh catalog', - 'engine.externalDatasource.tables': 'Tables', - 'engine.externalDatasource.validation': 'Validation', - // objectui#10862, slice 4 — the panel's two tabs and the import dialog: - // the tables tab (`SchemaBrowser`), the validation tab (`ValidationPanel`, - // one row per diff kind, total over the spec's kind union) and - // `ImportObjectDialog`. A `{hole}` a code span fills is rendered by - // `withNodes`. - 'engine.externalDatasource.browser.filter': 'Filter tables…', - 'engine.externalDatasource.browser.refresh': 'Refresh', - 'engine.externalDatasource.browser.introspecting': 'Introspecting remote schema…', - 'engine.externalDatasource.browser.noRemoteTables': - 'No remote tables found (check the datasource’s allowedSchemas).', - 'engine.externalDatasource.browser.noMatch': 'No tables match the filter.', - 'engine.externalDatasource.browser.colTable': 'Table', - 'engine.externalDatasource.browser.colColumns': 'Columns', - 'engine.externalDatasource.browser.colRows': 'Rows (est.)', - 'engine.externalDatasource.browser.import': 'Import', - 'engine.externalDatasource.browser.unavailable': - 'Federation is not enabled on this server. The {service} service must be registered for table browsing, drafting, and validation to work.', - 'engine.externalDatasource.check.unavailable': - 'Federation is not enabled on this server, so validation is unavailable.', - 'engine.externalDatasource.check.intro': - 'Check that every federated object on {datasource} still matches its live remote table.', - 'engine.externalDatasource.check.run': 'Run validation', - 'engine.externalDatasource.check.allMatchOne': 'All {count} object match the remote schema.', - 'engine.externalDatasource.check.allMatchOther': 'All {count} objects match the remote schema.', - 'engine.externalDatasource.check.divergeOne': '{diverged} of {count} object diverge from the remote schema.', - 'engine.externalDatasource.check.divergeOther': '{diverged} of {count} objects diverge from the remote schema.', - 'engine.externalDatasource.check.noObjects': - 'No federated objects are bound to this datasource yet. Import a table from the Tables tab to get started.', - 'engine.externalDatasource.check.diffsOne': '{count} diff', - 'engine.externalDatasource.check.diffsOther': '{count} diffs', - 'engine.externalDatasource.check.expectedActual': 'expected {expected}, actual {actual}', - 'engine.externalDatasource.diff.missingTable': 'Missing table', - 'engine.externalDatasource.diff.missingColumn': 'Missing column', - 'engine.externalDatasource.diff.typeMismatch': 'Type mismatch', - 'engine.externalDatasource.diff.nullabilityMismatch': 'Nullability mismatch', - 'engine.externalDatasource.diff.unmappedColumn': 'Unmapped column', - 'engine.externalDatasource.diff.pkMismatch': 'Primary-key mismatch', - 'engine.externalDatasource.diff.indexMismatch': 'Index mismatch', - 'engine.externalDatasource.diff.unmappedIndex': 'Unmapped index', - 'engine.externalDatasource.diff.defaultMismatch': 'Column default mismatch', - 'engine.externalDatasource.diff.unreachable': 'Not checked — remote unreachable', - 'engine.externalDatasource.import.title': 'Import as Object', - 'engine.externalDatasource.import.intro': - 'Map remote table {table} into an ObjectStack object bound to {datasource}. The remote schema is never modified.', - 'engine.externalDatasource.import.generating': 'Generating draft…', - 'engine.externalDatasource.import.done': 'Imported object {name}.', - 'engine.externalDatasource.import.doneNext': - 'Review its binding and run validation to confirm it matches the remote table.', - 'engine.externalDatasource.import.objectName': 'Object name', - 'engine.externalDatasource.import.reviewOne': '{count} column need review', - 'engine.externalDatasource.import.reviewOther': '{count} columns need review', - 'engine.externalDatasource.import.source': 'Generated source', - 'engine.externalDatasource.import.importing': 'Importing…', // Structural flow validation (flow-sim-validate) — canvas banner, Problems // panel, and the debug simulator. 'engine.flowValidate.nodeMissingId': 'A node is missing an id.', @@ -1340,16 +1204,9 @@ 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.', - 'engine.studio.data.tab.advanced': 'Advanced', // Standard create-dialog field labels (shared by object / app / flow / permission). 'engine.studio.app.nameLabel': 'App name', 'engine.studio.app.idLabel': 'Identifier', - 'engine.studio.data.nameLabel': 'Object name', - 'engine.studio.data.idLabel': 'Identifier', - 'engine.studio.auto.nameLabel': 'Flow name', - 'engine.studio.auto.idLabel': 'Identifier', - 'engine.studio.access.nameLabel': 'Name', - 'engine.studio.access.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. @@ -1362,8 +1219,6 @@ const ENGINE_STRINGS_EN: Record = { // 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.data.savedDraft': 'Object “{label}” saved as draft', - 'engine.studio.data.lastSaved': 'Saved {time}', 'engine.studio.publish': 'Publish', 'engine.studio.loading': 'Loading…', 'engine.studio.loadFailed': 'Failed to load', @@ -1417,117 +1272,6 @@ const ENGINE_STRINGS_EN: Record = { '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.', - // Pillar tab labels - 'engine.fieldType.text': 'Text', - 'engine.fieldType.textarea': 'Text Area', - 'engine.fieldType.email': 'Email', - 'engine.fieldType.url': 'URL', - 'engine.fieldType.phone': 'Phone', - 'engine.fieldType.password': 'Password', - 'engine.fieldType.secret': 'Secret', - 'engine.fieldType.markdown': 'Markdown', - 'engine.fieldType.html': 'HTML', - 'engine.fieldType.richtext': 'Rich Text', - 'engine.fieldType.number': 'Number', - 'engine.fieldType.currency': 'Currency', - 'engine.fieldType.percent': 'Percent', - 'engine.fieldType.date': 'Date', - 'engine.fieldType.datetime': 'Date/Time', - 'engine.fieldType.time': 'Time', - 'engine.fieldType.boolean': 'Checkbox', - 'engine.fieldType.toggle': 'Toggle', - 'engine.fieldType.select': 'Picklist', - 'engine.fieldType.multiselect': 'Multi-Select', - 'engine.fieldType.radio': 'Radio', - 'engine.fieldType.checkboxes': 'Checkboxes', - 'engine.fieldType.lookup': 'Lookup', - 'engine.fieldType.master_detail': 'Master-Detail', - 'engine.fieldType.tree': 'Tree', - 'engine.fieldType.user': 'User', - 'engine.fieldType.image': 'Image', - 'engine.fieldType.file': 'File', - 'engine.fieldType.avatar': 'Avatar', - 'engine.fieldType.video': 'Video', - 'engine.fieldType.audio': 'Audio', - 'engine.fieldType.formula': 'Formula', - 'engine.fieldType.summary': 'Rollup', - 'engine.fieldType.autonumber': 'Auto Number', - 'engine.fieldType.composite': 'Composite', - 'engine.fieldType.repeater': 'Repeater', - 'engine.fieldType.record': 'Record Map', - 'engine.fieldType.location': 'Location', - 'engine.fieldType.address': 'Address', - 'engine.fieldType.code': 'Code', - 'engine.fieldType.json': 'JSON', - 'engine.fieldType.color': 'Color', - 'engine.fieldType.rating': 'Rating', - 'engine.fieldType.slider': 'Slider', - 'engine.fieldType.signature': 'Signature', - 'engine.fieldType.qrcode': 'QR Code', - 'engine.fieldType.progress': 'Progress', - 'engine.fieldType.tags': 'Tags', - 'engine.fieldType.vector': 'Vector', - // objectui#11793 — the one-line description under each type in the field - // inspector's Type picker. `@objectstack/spec` carries no per-type - // description (its `FieldType` enum has no `.describe()`), so they live here. - 'engine.fieldTypeDesc.text': 'A single line of text.', - 'engine.fieldTypeDesc.textarea': 'Several lines of plain text.', - 'engine.fieldTypeDesc.email': 'An email address.', - 'engine.fieldTypeDesc.url': 'A web address.', - 'engine.fieldTypeDesc.phone': 'A phone number.', - 'engine.fieldTypeDesc.password': 'Text that is masked when it is read back.', - 'engine.fieldTypeDesc.secret': 'A credential such as an API key, encrypted at rest and masked when read back.', - 'engine.fieldTypeDesc.markdown': 'Formatted text written in Markdown.', - 'engine.fieldTypeDesc.html': 'HTML markup.', - 'engine.fieldTypeDesc.richtext': 'Formatted text, edited visually.', - 'engine.fieldTypeDesc.number': 'A number, whole or decimal.', - 'engine.fieldTypeDesc.currency': 'An amount of money.', - 'engine.fieldTypeDesc.percent': 'A percentage.', - 'engine.fieldTypeDesc.date': 'A calendar date.', - 'engine.fieldTypeDesc.datetime': 'A date with a time of day.', - 'engine.fieldTypeDesc.time': 'A time of day.', - 'engine.fieldTypeDesc.boolean': 'Yes or no, shown as a checkbox.', - 'engine.fieldTypeDesc.toggle': 'Yes or no, shown as a switch.', - 'engine.fieldTypeDesc.select': 'One value from a list of options.', - 'engine.fieldTypeDesc.multiselect': 'Several values from a list of options.', - 'engine.fieldTypeDesc.radio': 'One value, shown as radio buttons.', - 'engine.fieldTypeDesc.checkboxes': 'Several values, shown as checkboxes.', - 'engine.fieldTypeDesc.lookup': 'A link to a record of another object.', - 'engine.fieldTypeDesc.master_detail': 'A link to the parent record that owns this one.', - 'engine.fieldTypeDesc.tree': 'A link to a parent record of the same object.', - 'engine.fieldTypeDesc.user': 'A link to a user, such as an owner or an assignee.', - 'engine.fieldTypeDesc.image': 'An uploaded image.', - 'engine.fieldTypeDesc.file': 'An uploaded file.', - 'engine.fieldTypeDesc.avatar': 'A profile picture.', - 'engine.fieldTypeDesc.video': 'An uploaded video.', - 'engine.fieldTypeDesc.audio': 'An uploaded audio clip.', - 'engine.fieldTypeDesc.formula': 'A value calculated by a formula.', - 'engine.fieldTypeDesc.summary': 'A value rolled up from related records.', - 'engine.fieldTypeDesc.autonumber': 'A number assigned to each new record.', - 'engine.fieldTypeDesc.composite': 'A group of sub-fields stored together.', - 'engine.fieldTypeDesc.repeater': 'A repeating list of sub-field groups.', - 'engine.fieldTypeDesc.record': 'Groups of sub-fields, each stored under its own name.', - 'engine.fieldTypeDesc.location': 'GPS coordinates.', - 'engine.fieldTypeDesc.address': 'A structured postal address.', - 'engine.fieldTypeDesc.code': 'Source code such as JSON, SQL or JavaScript.', - 'engine.fieldTypeDesc.json': 'Structured JSON data.', - 'engine.fieldTypeDesc.color': 'A color.', - 'engine.fieldTypeDesc.rating': 'A star rating.', - 'engine.fieldTypeDesc.slider': 'A number picked on a slider.', - 'engine.fieldTypeDesc.signature': 'A digital signature.', - 'engine.fieldTypeDesc.qrcode': 'A QR code or barcode.', - 'engine.fieldTypeDesc.progress': 'A value shown as a progress bar.', - 'engine.fieldTypeDesc.tags': 'A simple list of tags.', - 'engine.fieldTypeDesc.vector': 'Vector embeddings for AI semantic search.', - 'engine.fieldCategory.text': 'Text', - 'engine.fieldCategory.number': 'Number', - 'engine.fieldCategory.date': 'Date / time', - 'engine.fieldCategory.logic': 'Logic', - 'engine.fieldCategory.selection': 'Selection', - 'engine.fieldCategory.relation': 'Relation', - 'engine.fieldCategory.media': 'Media', - 'engine.fieldCategory.calculated': 'Calculated', - 'engine.fieldCategory.advanced': 'Advanced', 'engine.studio.aiCopilot': 'AI copilot', // objectui#12725 — the Studio right slot: properties OR the copilot. 'engine.studio.slot.label': 'Right panel', @@ -1598,7 +1342,6 @@ const ENGINE_STRINGS_EN: Record = { '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.auto.deepLinkMissing': 'The link names flow “{name}”, which is not here.', 'engine.studio.designer.search': 'Search…', 'engine.studio.designer.select': 'Select…', 'engine.studio.designer.pickDate': 'Pick a date…', @@ -1690,57 +1433,6 @@ const ENGINE_STRINGS_EN: Record = { '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.settings.basics': 'Basics', - 'engine.studio.settings.noInspector': 'No default object inspector registered.', - 'engine.studio.settings.semanticRoles': 'Semantic roles', - 'engine.studio.settings.semanticHint': 'Applied consistently across forms, lists and detail pages (ADR-0085)', - 'engine.studio.settings.nameField': 'Record name field (nameField) — shown in titles, links and references', - 'engine.studio.settings.autoDerive': '(auto-derived)', - 'engine.studio.settings.stageField': 'Lifecycle field (stageField) — the detail page progress bar renders its options', - 'engine.studio.settings.autoDetect': '(auto-detect status/stage-like field names)', - 'engine.studio.settings.stageNone': 'None — this object has no linear flow; hide the progress bar', - 'engine.studio.settings.highlightFields': 'Highlight fields (highlightFields) — the most important fields on this object, in display order; surfaces that show a record at a glance draw on them, each taking as many as it has room for', - 'engine.studio.settings.removeField': 'Remove {field}', - 'engine.studio.settings.addFieldOption': '+ Add field…', - 'engine.studio.settings.undeclared': '(not declared — surfaces pick heuristically)', - 'engine.studio.settings.sharing': 'Record sharing (OWD)', - 'engine.studio.settings.sharingHint': 'Baseline record visibility applied before positions and sharing rules (ADR-0056/0090)', - 'engine.studio.settings.sharingModel': 'Sharing model (sharingModel) — who can see and edit records another user owns', - 'engine.studio.settings.sharingUnset': '(not set — publishing is refused)', - 'engine.studio.settings.sharingPrivate': 'Private — owner only', - 'engine.studio.settings.sharingPublicRead': 'Public read — everyone reads, only the owner writes', - 'engine.studio.settings.sharingPublicReadWrite': 'Public read/write — everyone reads and writes', - 'engine.studio.settings.sharingControlledByParent': 'Controlled by parent — inherited from the master record', - 'engine.studio.settings.sharingDescUnset': - 'Not set — publishing this object is REFUSED (security-owd-unset). The runtime does fall back to Private (ADR-0090 D1), but the baseline has to be an authored decision, not an accident. Pick a model — Private is the same behaviour, chosen on purpose.', - 'engine.studio.settings.sharingDescPrivate': - 'Only the owner (plus users granted via positions or sharing rules) can access a record. Read / Edit permissions then apply to owned records only.', - 'engine.studio.settings.sharingDescPublicRead': - 'Every user in the tenant can read all records; only the owner can edit.', - 'engine.studio.settings.sharingDescPublicReadWrite': - 'Every user in the tenant can read and edit all records.', - 'engine.studio.settings.sharingDescControlledByParent': - 'Record visibility is inherited from the parent (master) record through its master-detail relationship.', - 'engine.studio.settings.capabilities': 'Capabilities (enable.*)', - 'engine.studio.settings.capabilitiesHint': 'Record-page features — every switch here is enforced by the runtime', - 'engine.studio.settings.capTrackHistory': 'History tab (trackHistory)', - 'engine.studio.settings.capTrackHistoryDesc': - 'Shows the read-only History tab (audit trail) on record pages. Pair with per-field history tracking for human-readable diffs; audit capture itself is always on for compliance.', - 'engine.studio.settings.capFiles': 'Attachments (files)', - 'engine.studio.settings.capFilesDesc': - 'Generic Attachments panel on record pages (upload/download/delete). Off = the server also rejects new attachments targeting this object.', - 'engine.studio.settings.capFeeds': 'Comments & feed (feeds)', - 'engine.studio.settings.capFeedsDesc': - 'Record discussion panel with @mentions and reactions. Off = the panel is hidden AND new comments are rejected server-side.', - 'engine.studio.settings.capActivities': 'Activity timeline (activities)', - 'engine.studio.settings.capActivitiesDesc': - 'Mirrors create/update/delete into the record timeline. Off = no timeline rows for this object (the compliance audit log is unaffected).', - 'engine.studio.settings.capSearchable': 'Global search (searchable)', - 'engine.studio.settings.capSearchableDesc': - "Include this object's records in cross-object global search. Off = records no longer appear in global search results. Which fields match is configured per-object via searchableFields, not here.", - 'engine.studio.settings.capClone': 'Cloning (clone)', - 'engine.studio.settings.capCloneDesc': - 'Allow duplicating records from the record page / API. Off = the clone endpoint returns 403 for this object.', 'engine.studio.pkg.writable': 'Writable', 'engine.studio.pkg.heading': 'Packages (apps)', 'engine.studio.pkg.none': 'No app packages yet', @@ -1755,107 +1447,6 @@ const ENGINE_STRINGS_EN: Record = { '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.', - // Interfaces pillar - 'engine.studio.if.pickLeft': 'Select a menu item on the left', - // objectui#7254 — the tooltip that keeps the DEVELOPER identity reachable - // after the breadcrumb/caption stopped printing it beside a localized - // label. Prefixed rather than bare: `dashboard · customer_dashboard` on - // its own does not say what it is, and it is also what the rail item's - // own tooltip carries — three elements sharing one tooltip string is a - // page nothing (a person or a test) can address unambiguously. - 'engine.studio.if.internalId': 'Internal id', - 'engine.studio.if.navHeading': '{app} · Navigation', - 'engine.studio.if.editNavTitle': 'Edit navigation (drag to reorder / rename / add-remove)', - 'engine.studio.if.doneEditTitle': 'Done editing', - 'engine.studio.if.noApp': 'This package has no app yet.', - 'engine.studio.if.noNavItems': 'No nav items yet — (click “Edit” above to add)', - 'engine.studio.if.previewIsRuntime': 'Live preview', - 'engine.studio.if.modeDesign': 'Design', - 'engine.studio.if.modeRun': 'Run', - // objectui#12522 — a source page (`kind: 'html'` / `'react'`) in the Interfaces pillar. - 'engine.studio.if.modeSourcePage': - 'Run looks the same as Design here: a page written in {kind} has no designer chrome to hide.', - 'engine.studio.if.pageSaved': 'Saved', - 'engine.studio.if.pageSavedAt': 'Saved {time}', - 'engine.studio.if.pageUnsaved': 'Unsaved', - 'engine.studio.if.pageProps.label': 'Label', - 'engine.studio.if.pageProps.identifier': 'Identifier', - 'engine.studio.if.pageProps.kindFixed': 'Chosen when the page was created; it does not change here.', - 'engine.studio.if.pageProps.navEntries': 'Navigation entries', - 'engine.studio.if.pageProps.noNavEntries': 'No navigation entry opens this page.', - 'engine.studio.if.tabCanvas': 'Canvas', - 'engine.studio.if.noAppTitle': 'This package has no app yet', - 'engine.studio.if.noAppHint': 'Create an app to design its navigation and interfaces.', - // objectui#12508 — the canvas of an object that has only a draft: no runtime yet. - 'engine.studio.if.objectUnpublishedTitle': 'This object is not published yet', - 'engine.studio.if.objectUnpublishedHint': 'Publish the package to see its records here.', - /* objectui#6795 part C — the three designer registries (`preview-registry`, - * `inspector-registry`, `default-inspector-registry`) were plain `Map`s with - * no change notification, so a consumer that read one before registration - * landed NEVER recovered (measured on #6795: `late inspector rendered: - * false`). Part A has since landed in two steps (objectui#11939): the - * registries are observable and their readers re-render on registration - * (step 1), and the package entry registers the built-in designers from a - * chunk it loads with a dynamic `import()` (step 2). So a reader that renders - * before that chunk arrives shows these strings and then the designer; a - * session whose chunk never loads (or that registers no designers at all) - * keeps them. - * - * Every string in this family still states only what is TRUE at the moment - * it renders and promises nothing: no "loading…", no "try again", no - * spinner. Recovery is now real, but whether it comes depends on a chunk - * the string cannot see, so promising it would be a new false statement. - * - * The retired `engine.studio.if.readonlyPreview` ("{type} shows a read-only - * preview for now; design support is in progress.") was false twice over: this - * branch renders NO preview at all, and page design support exists — the - * sentence blamed the platform's roadmap for a chunk that did not load. It is - * split in two because the two causes are genuinely different and - * `listMetadataPreviewTypes()` tells them apart. */ - 'engine.studio.if.designersMissing': - 'No metadata designers are registered in this session, so {type} cannot be previewed or designed here.', - 'engine.studio.if.noDesigner': - 'No designer is registered for {type}, so it cannot be previewed or designed here.', - 'engine.studio.if.objectHintPre': 'Runtime list preview · edit fields / structure in the ', - 'engine.studio.if.objectHintPost': ' pillar', - /** - * objectui#12455 — the Interfaces rail's kind tags and its "+" kind picker. - * The four kinds the console also names as metadata types (object, page, - * dashboard, report) are named by `translateMetadataType`, so the rail and - * the header chip say one word; these are the rest. `engine.inspector.appNav.*` - * stays the Type field's own wording. - */ - 'engine.studio.if.kind.view': 'View', - 'engine.studio.if.kind.url': 'Link', - 'engine.studio.if.kind.action': 'Action', - 'engine.studio.if.kind.component': 'Component', - 'engine.studio.if.kind.doc': 'Doc', - 'engine.studio.if.kind.group': 'Group', - 'engine.studio.if.kind.separator': 'Separator', - 'engine.studio.if.kindDesc.object': 'A list view or one record', - 'engine.studio.if.kindDesc.page': 'A custom page built from blocks', - 'engine.studio.if.kindDesc.dashboard': 'Widgets over filters', - 'engine.studio.if.kindDesc.report': 'A saved report', - 'engine.studio.if.kindDesc.url': 'An external URL, same or new tab', - 'engine.studio.if.kindDesc.action': 'Runs a global action from the menu', - 'engine.studio.if.kindDesc.component': 'A registered component, like the approvals inbox', - 'engine.studio.if.kindDesc.doc': 'A doc page, a book, or both', - 'engine.studio.if.kindDesc.group': 'A titled section of the menu', - 'engine.studio.if.kindDesc.separator': 'A divider, no label, opens nothing', - 'engine.studio.if.addMenuTitle': 'Add to navigation', - 'engine.studio.if.emptyGroup': 'No items in this package', - 'engine.studio.if.openInData': 'Open in Data', - // objectui#12725 — the status line above the canvas, and the item's ⋯ menu. - 'engine.studio.if.status.design': 'Design', - 'engine.studio.if.status.run': 'Live preview', - 'engine.studio.if.status.unpublished': 'includes unpublished changes', - 'engine.studio.if.canvasHint.pick': 'Click an element → the right panel shows its properties', - 'engine.studio.if.canvasHint.selected': 'Selected: {label} · Esc to deselect', - 'engine.studio.if.more': 'More', - 'engine.studio.if.viewDefinition': 'View definition', - 'engine.studio.if.copyJson': 'Copy JSON', - 'engine.studio.if.copyFailed': 'Could not copy to the clipboard', - 'engine.studio.if.definitionTitle': '{label} · Definition', 'engine.studio.nav.eyebrow': 'Nav item · {kind}', 'engine.studio.nav.icon': 'Icon', 'engine.studio.nav.visibleWhen': 'Visible when', @@ -1963,34 +1554,6 @@ const ENGINE_STRINGS_EN: Record = { '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.', - // Data pillar - 'engine.studio.data.newFieldLabel': 'New field', - 'engine.studio.data.nameFieldLabel': 'Name', - 'engine.studio.data.idExists': 'Identifier “{name}” already exists', - 'engine.studio.data.fieldCount': '{count} fields', - 'engine.studio.data.pickObject': 'Select an object', - 'engine.studio.data.objects': 'Objects', - 'engine.studio.data.searchObjects': 'Search objects…', - 'engine.studio.data.noObjects': 'No objects yet — click “+” above to create one', - 'engine.studio.data.labelPlaceholder': 'Display name (e.g. Repair Ticket)', - 'engine.studio.data.idPlaceholder': 'Identifier (e.g. repair_ticket)', - // [`7a90afdf9`] The create dialog's third field. `新建对象` used to ask for - // exactly two things and produce an object the publish door refuses. - 'engine.studio.data.owdLabel': 'Record sharing (OWD) — who can see records another user owns', - 'engine.studio.data.owdHint': - 'Required to publish, and changeable later under Settings → Record sharing. Private matches what the runtime already does when no model is set.', - 'engine.studio.data.newObject': 'New object', - 'engine.studio.data.readOnlyPackage': 'This package is read-only — switch to or create a writable package to add objects', - 'engine.studio.data.firstObjectTitle': 'Start with your first object', - 'engine.studio.data.firstObjectHint': - 'Objects are your app’s data foundation (e.g. “Orders”, “Customers”). Create one below; then design its fields, forms, and automations, and publish once at the end.', - 'engine.studio.data.tab.records': 'Records', - 'engine.studio.data.tab.form': 'Form', - 'engine.studio.data.tab.rules': 'Validations', - 'engine.studio.data.tab.settings': 'Settings', - 'engine.studio.data.tab.hooks': 'Hooks', - 'engine.studio.data.tab.actions': 'Actions', - 'engine.studio.data.tab.api': 'API', // Actions view 'engine.studio.actions.none': 'No actions on this object.', 'engine.studio.actions.newButton': 'New action', @@ -2028,38 +1591,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.hooks.async': 'async', 'engine.studio.hooks.pick': 'Select a hook to edit it.', 'engine.studio.hooks.newLabel': 'New hook', - 'engine.studio.data.addField': 'Add field', - 'engine.studio.data.addFieldCreate': 'Create', - 'engine.studio.data.addFieldApiNameNotDerived': 'The label has no Latin letters to make an API name from. Type one: lowercase letters, digits and underscores.', - 'engine.studio.data.addFieldApiNameTaken': 'Another field is already named {name}. Type a different API name.', - 'engine.studio.data.editFieldProps': 'Edit field properties', - 'engine.studio.data.draftObjectTitle': 'Unpublished new object', - 'engine.studio.data.draftObjectHint': - 'The Records grid queries real data, but this object has no table until it’s published. Design fields and groups in “Form · Layout” first, then click “Publish” in the top bar — after publishing, this becomes its live data grid.', - 'engine.studio.data.goDesignFields': 'Go to “Form · Layout” to design fields', - 'engine.studio.data.form.layout': 'Layout', - 'engine.studio.data.form.preview': 'Preview', - // Two captions for the same tab: `layoutBadge` additionally CLAIMS pending - // local edits, so it may only render when there are some and they can be - // saved; `layoutBadgeClean` is the neutral caption for every other case - // (objectui#4036). - 'engine.studio.data.form.layoutBadge': 'Draft layout — your unsaved changes', - 'engine.studio.data.form.layoutBadgeClean': 'Draft layout', - // objectui#11781 — a read-only package has no draft layout: it says what it is. - 'engine.studio.data.form.layoutBadgeReadOnly': 'Read-only layout — this package cannot be edited', - 'engine.studio.data.form.previewBadge': 'Preview the form as the app renders it', - // objectui#12515 — Preview renders the draft definition; the chip says so, - // and the toggle (shown only beside a published version) picks the source. - 'engine.studio.data.form.draftChip': 'Draft preview', - 'engine.studio.data.form.draftChipTitle': - 'This preview renders the draft definition, unsaved changes included. Nothing is saved from here.', - 'engine.studio.data.form.sourceLabel': 'Preview source', - 'engine.studio.data.form.sourceDraft': 'Draft', - 'engine.studio.data.form.sourcePublished': 'Published', - 'engine.studio.data.fieldProps': 'Field properties', - 'engine.studio.data.groupProps': 'Group properties', - 'engine.studio.data.fieldInspectorMissing': - 'No field inspector is registered in this session, so this field’s properties cannot be edited here.', 'engine.studio.designer.group.kind': 'Group', 'engine.studio.designer.group.settings': 'Group settings', 'engine.studio.designer.group.nameLabel': 'Group name', @@ -2069,120 +1600,8 @@ const ENGINE_STRINGS_EN: Record = { '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).', - // Automations pillar - 'engine.studio.auto.nodeStart': 'Start', - 'engine.studio.auto.nodeEnd': 'End', - 'engine.studio.auto.savedDraft': 'Automation “{label}” saved as draft', - 'engine.studio.auto.defaultOff': 'Off by default · review before enabling', - 'engine.studio.auto.heading': 'Automations', - 'engine.studio.auto.on': 'On', - 'engine.studio.auto.off': 'Off', - 'engine.studio.auto.offTitle': 'Disabled — will not run', - 'engine.studio.auto.onBound': 'Enabled · bound to its trigger', - 'engine.studio.auto.onUnbound': 'Enabled · no trigger (run manually)', - // objectui#11779 — an enabled flow whose declared trigger is not armed on this - // deployment; the title is for a backend that sends no `reason` (with one, - // the platform's sentence is shown instead). - 'engine.studio.auto.notRunning': 'Not running here', - 'engine.studio.auto.notRunningTitle': 'Enabled, but its trigger is not armed on this deployment', - 'engine.studio.auto.unpublishedTitle': 'Not deployed yet — publish to deploy it', - // objectui#12349 — the engine's registration verdict on a flow. A refused - // flow is titled with the engine's own message, as sent; an unregistered one - // reads "Not running here", titled by the row below. - 'engine.studio.auto.refused': 'Refused by the engine', - 'engine.studio.auto.unregisteredTitle': 'The automation engine on this deployment holds no registration for this flow', - 'engine.studio.auto.enabled': 'Enabled', - 'engine.studio.auto.disabled': 'Disabled', - 'engine.studio.auto.enableTitle': 'Enable this automation (publish to apply)', - 'engine.studio.auto.disableTitle': 'Disable this automation (publish to apply)', - 'engine.studio.auto.enabledToast': 'Enabled — publish to apply', - 'engine.studio.auto.disabledToast': 'Disabled — publish to apply', - 'engine.studio.auto.newTitle': 'New automation', - 'engine.studio.auto.none': 'No automations yet — click “+” above to create one', - 'engine.studio.auto.emptyMain': 'No automations in this package yet.', - 'engine.studio.auto.namePlaceholder': 'Name (e.g. Offer Notice)', - 'engine.studio.auto.idPlaceholder': 'Identifier (e.g. offer_notice)', // objectui#11788 — the New automation dialog's trigger choice, left unset. 'engine.studio.newAutoTrigger.later': 'Choose later on the Start node', - // objectui#11861 — the New automation dialog's starting points (`flowPresets.ts`). - 'engine.studio.auto.presets': 'When it runs', - 'engine.studio.auto.preset.created': 'When a record is created', - 'engine.studio.auto.preset.updated': 'When a record is updated', - 'engine.studio.auto.preset.deleted': 'When a record is deleted', - 'engine.studio.auto.preset.manual': 'When a button or another automation starts it', - 'engine.studio.auto.preset.needsObject': 'Choose the object it watches.', - 'engine.studio.auto.canvasHint': 'Visual orchestration · click a node to configure', - 'engine.studio.auto.config': 'Configuration', - 'engine.studio.auto.emptyLine1': 'Click a node on the canvas,', - 'engine.studio.auto.emptyLine2': 'and its configuration appears here.', - 'engine.studio.auto.designersMissing': - 'No metadata designers are registered in this session, so this flow cannot be designed here.', - // objectui#12454 — the rail's groups and legend: a flow's trigger kind, and - // the trailing group of flows that name no object. - 'engine.studio.auto.group.noObject': 'No object', - 'engine.studio.auto.kind.record': 'Record-triggered', - 'engine.studio.auto.kind.scheduled': 'Scheduled', - 'engine.studio.auto.kind.screen': 'Screen', - 'engine.studio.auto.kind.autolaunched': 'Autolaunched', - 'engine.studio.auto.legend': 'Legend', - // Access pillar - 'engine.studio.access.created': 'Permission set “{label}” created', - 'engine.studio.access.title': 'Permission matrix', - 'engine.studio.access.subtitle': 'Objects × CRUD · field-level R/W', - 'engine.studio.access.bannerTitle': - 'This matrix lists only the objects this package declares, and “Save” merges just that slice — grants contributed by other packages are preserved. Edits are saved as package drafts and go live when you Publish the package (top bar), exactly like Data and Interfaces.', - 'engine.studio.access.banner': 'This package’s objects · saved as draft', - // objectui#11781 — a read-only package saves no draft; objectui#12453 makes this the header link's title. - 'engine.studio.access.bannerTitleReadOnly': - 'This matrix lists only the objects this package declares. The package is read-only: its permission sets can be viewed here, but not changed.', - // ADR-0090 D5/D9 — pending suggested audience bindings (isDefault sets - // awaiting the admin's confirm; the server never auto-binds). - 'engine.studio.access.suggestPromptEveryone': 'This package suggests granting "{set}" to all signed-in users (Everyone).', - 'engine.studio.access.suggestPromptGuest': 'This package suggests granting "{set}" to unauthenticated visitors (Guest).', - 'engine.studio.access.suggestConfirm': 'Grant', - 'engine.studio.access.suggestConfirming': 'Granting…', - 'engine.studio.access.suggestDismiss': 'Dismiss', - 'engine.studio.access.suggestConfirmedToast': '"{set}" is now granted to {anchor}.', - 'engine.studio.access.suggestDismissedToast': 'Suggestion for "{set}" dismissed.', - // objectstack#22801 → A — an accept takes effect for the deployment; a - // dismiss on an accepted row revokes it; a `guest` row cannot be accepted. - 'engine.studio.access.suggestGuestUnsupported': 'Cannot be granted here: the Guest position takes no sets from accepted suggestions.', - 'engine.studio.access.suggestAcceptedState': '"{set}" was accepted for all signed-in users (Everyone).', - 'engine.studio.access.suggestNotInForceState': '"{set}" was accepted but is not in force yet: Everyone did not change on this request. The server log says why.', - 'engine.studio.access.suggestDismissedState': '"{set}" is not granted: the suggestion was dismissed or revoked.', - 'engine.studio.access.suggestRevoke': 'Revoke', - 'engine.studio.access.suggestRevoking': 'Revoking…', - 'engine.studio.access.suggestRevokedToast': 'Acceptance of "{set}" revoked.', - 'engine.studio.access.heading': 'Permission sets', - 'engine.studio.access.search': 'Search permissions…', - 'engine.studio.access.none': 'No permission sets yet — click “+” above to create one', - 'engine.studio.access.labelPlaceholder': 'Display name (e.g. Sales permissions)', - 'engine.studio.access.idPlaceholder': 'Identifier (e.g. sales_perms)', - 'engine.studio.access.new': 'New permission set', - 'engine.studio.access.emptyMain': 'No permission sets in this package yet.', - // objectui#12456 — the open set's item header and its five tabs. - 'engine.studio.access.tab.objects': 'Objects', - 'engine.studio.access.tab.capabilities': 'System capabilities', - 'engine.studio.access.tab.rls': 'Row-level security', - 'engine.studio.access.tab.tabs': 'Tab visibility', - 'engine.studio.access.tab.admin': 'Delegated admin', - 'engine.studio.access.grantedOne': '{count} object granted', - 'engine.studio.access.grantedOther': '{count} objects granted', - 'engine.studio.access.overridesOne': '{count} field override', - 'engine.studio.access.overridesOther': '{count} field overrides', - 'engine.studio.access.objectCountOne': '{count} object', - 'engine.studio.access.objectCountOther': '{count} objects', - 'engine.studio.access.objectCountFiltered': '{shown} of {count} objects', - 'engine.studio.access.more': 'More actions', - 'engine.studio.access.colOwd': 'OWD', - 'engine.studio.access.legend.create': 'Create', - 'engine.studio.access.legend.read': 'Read', - 'engine.studio.access.legend.edit': 'Edit', - 'engine.studio.access.legend.delete': 'Delete', - 'engine.studio.access.legend.transfer': 'Transfer', - 'engine.studio.access.legend.viewAll': 'View all', - 'engine.studio.access.legend.modifyAll': 'Modify all', - 'engine.studio.access.bulkHint': 'Bulk actions (R · CRUD · All · None) appear on the row under the pointer or with keyboard focus.', // ── Record Sharing Baseline (OWD) overview (objectui#2505) ────────────── 'engine.studio.owd.railLabel': 'Record sharing (OWD)', @@ -2203,89 +1622,6 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.owd.masterTip': 'Controlled by parent — record visibility is inherited from the master record.', - // ── Access explain panel (ADR-0090 D6 — "why can this user access?") ──── - 'engine.studio.access.explain.open': 'Explain access', - 'engine.studio.access.explain.title': 'Why can this user access?', - 'engine.studio.access.explain.description': - 'Runs the same evaluation pipeline as enforcement and reports what each layer contributed — permission set → position → binding chain, OWD/sharing verdicts.', - 'engine.studio.access.explain.user': 'User', - 'engine.studio.access.explain.self': 'Me (calling principal)', - 'engine.studio.access.explain.pickUser': 'Select user…', - 'engine.studio.access.explain.clearUser': 'Reset to me', - 'engine.studio.access.explain.pickUserTitle': 'Select the user to explain', - 'engine.studio.access.explain.object': 'Object', - 'engine.studio.access.explain.objectPlaceholder': 'Object api-name', - 'engine.studio.access.explain.objectSelect': 'Select an object…', - 'engine.studio.access.explain.operation': 'Operation', - 'engine.studio.access.explain.run': 'Explain', - 'engine.studio.access.explain.running': 'Explaining…', - 'engine.studio.access.explain.allowedLine': '{operation} on “{object}” is ALLOWED for this principal.', - 'engine.studio.access.explain.deniedLine': '{operation} on “{object}” is DENIED for this principal.', - 'engine.studio.access.explain.principal': 'Principal', - 'engine.studio.access.explain.positions': 'Positions', - 'engine.studio.access.explain.permissionSets': 'Permission sets', - 'engine.studio.access.explain.layers': 'Evaluation pipeline', - 'engine.studio.access.explain.contributors': 'Contributed by', - 'engine.studio.access.explain.readFilter': 'Composed row filter (read)', - 'engine.studio.access.explain.empty': 'Pick an object and operation, then click “Explain”.', - 'engine.studio.access.explain.failed': 'Explain failed', - 'engine.studio.access.explain.forbidden': - 'You cannot explain that user — it requires the manage_users capability or a delegated admin scope covering them (ADR-0090 D6/D12).', - 'engine.studio.access.explain.layer.principal': 'Principal resolution', - 'engine.studio.access.explain.layer.required_permissions': 'Required capabilities', - 'engine.studio.access.explain.layer.object_crud': 'Object CRUD grant', - 'engine.studio.access.explain.layer.fls': 'Field-level security', - 'engine.studio.access.explain.layer.owd_baseline': 'Record baseline (OWD)', - 'engine.studio.access.explain.layer.depth': 'Scope depth', - 'engine.studio.access.explain.layer.sharing': 'Sharing', - 'engine.studio.access.explain.layer.vama_bypass': 'View/Modify All bypass', - 'engine.studio.access.explain.layer.rls': 'Row-level security', - 'engine.studio.access.explain.verdict.grants': 'grants', - 'engine.studio.access.explain.verdict.denies': 'denies', - 'engine.studio.access.explain.verdict.narrows': 'narrows', - 'engine.studio.access.explain.verdict.widens': 'widens', - 'engine.studio.access.explain.verdict.neutral': 'neutral', - 'engine.studio.access.explain.verdict.not_applicable': 'n/a', - 'engine.studio.access.explain.op.read': 'Read', - 'engine.studio.access.explain.op.create': 'Create', - 'engine.studio.access.explain.op.update': 'Update', - 'engine.studio.access.explain.op.delete': 'Delete', - 'engine.studio.access.explain.op.transfer': 'Transfer', - 'engine.studio.access.explain.op.restore': 'Restore', - 'engine.studio.access.explain.op.purge': 'Purge', - // [C2 / ADR-0095] record-grained explain - 'engine.studio.access.explain.record': 'Record (optional)', - 'engine.studio.access.explain.recordPlaceholder': 'Record id — explain one concrete row', - 'engine.studio.access.explain.pickRecord': 'Pick', - 'engine.studio.access.explain.clearRecord': 'Clear record', - 'engine.studio.access.explain.pickRecordTitle': 'Select the record to explain', - 'engine.studio.access.explain.recordVisibleLine': 'Record {recordId} is VISIBLE for this operation.', - 'engine.studio.access.explain.recordHiddenLine': 'Record {recordId} is NOT visible for this operation.', - 'engine.studio.access.explain.decidedBy': 'Decided by', - 'engine.studio.access.explain.postureHint': 'The posture rung the principal was evaluated at (ADR-0095 D2).', - 'engine.studio.access.explain.posture.PLATFORM_ADMIN': 'Platform admin', - 'engine.studio.access.explain.posture.TENANT_ADMIN': 'Tenant admin', - 'engine.studio.access.explain.posture.MEMBER': 'Member', - 'engine.studio.access.explain.posture.EXTERNAL': 'External', - 'engine.studio.access.explain.layer.tenant_isolation': 'Tenant isolation (Layer 0)', - 'engine.studio.access.explain.kernelTier.layer_0_tenant': 'Tenant wall', - 'engine.studio.access.explain.kernelTier.layer_1_business': 'Business RLS', - 'engine.studio.access.explain.kernelTierHint': 'Layer 0 = the always-first org wall; Layer 1 = business row-level security.', - 'engine.studio.access.explain.recordOutcome': 'This record', - 'engine.studio.access.explain.outcome.admitted': 'admitted', - 'engine.studio.access.explain.outcome.excluded': 'excluded', - 'engine.studio.access.explain.outcome.not_evaluated': 'not evaluated', - 'engine.studio.access.explain.matches': 'record matches', - 'engine.studio.access.explain.noMatch': 'record does not match', - 'engine.studio.access.explain.effectiveRowFilter': 'Effective row filter', - 'engine.studio.access.explain.ruleKind.tenant_filter': 'Tenant filter', - 'engine.studio.access.explain.ruleKind.owd_baseline': 'OWD baseline', - 'engine.studio.access.explain.ruleKind.ownership': 'Ownership', - 'engine.studio.access.explain.ruleKind.record_share': 'Record share', - 'engine.studio.access.explain.ruleKind.sharing_rule': 'Sharing rule', - 'engine.studio.access.explain.ruleKind.team': 'Team', - 'engine.studio.access.explain.ruleKind.territory': 'Territory', - 'engine.studio.access.explain.ruleKind.rls_policy': 'RLS policy', // ── AppNavCanvas (nav-tree editor, used by Studio + AppPreview) ───────── 'engine.appNav.heading': 'Navigation', @@ -2427,64 +1763,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.sharingRule.clone.sourceOff': '已声明的规则「{source}」已停用。', 'engine.sharingRule.clone.openCopy': '打开新规则', 'engine.sharingRule.clone.done': '完成', - 'engine.datasource.deleteTitle': '删除数据源“{item}”?', - 'engine.datasource.deleteBody': '连接 {name} 将被移除。此操作不可撤销。', - 'engine.datasource.delete': '删除', - 'engine.datasource.title': '数据源', - 'engine.datasource.subtitle': '连接外部数据库,并将其中的表同步为对象。', - 'engine.datasource.refresh': '刷新', - 'engine.datasource.new': '新建数据源', - 'engine.datasource.loading': '加载中…', - 'engine.datasource.empty': '还没有数据源。点击{action}连接一个。', - 'engine.datasource.test': '测试', - 'engine.datasource.sync': '同步对象', - 'engine.datasource.edit': '编辑', - 'engine.datasource.statusOk': '已连接', - 'engine.datasource.statusError': '连接失败', - 'engine.datasource.statusBlocked': '被策略阻止', - 'engine.datasource.statusUnvalidated': '未连接', - 'engine.datasource.editTitle': '编辑“{name}”', - 'engine.datasource.editDescription': '更新连接。凭据留空则保留当前凭据。', - 'engine.datasource.newDescription': '连接外部数据库。凭据会加密保存,绝不写入元数据。', - 'engine.datasource.name': '名称', - 'engine.datasource.nameInvalid': '仅限小写字母、数字和下划线,且不能以数字开头。', - 'engine.datasource.label': '显示名称', - 'engine.datasource.labelPlaceholder': '便于识别的名称', - 'engine.datasource.driver': '驱动', - 'engine.datasource.driverPlaceholder': '选择驱动', - 'engine.datasource.schemaMode': '结构模式', - 'engine.datasource.schemaModeManaged': '托管(表结构由 ObjectStack 管理)', - 'engine.datasource.schemaModeExternal': '外部(按原样读取现有表结构)', - 'engine.datasource.schemaModeValidateOnly': '仅校验', - 'engine.datasource.testConnection': '测试连接', - 'engine.datasource.connectionFailed': '连接失败', - 'engine.datasource.connectionOk': '连接正常', - 'engine.datasource.connectionOkLatency': '连接正常({latency} 毫秒)', - 'engine.datasource.saveChanges': '保存更改', - 'engine.datasource.create': '创建数据源', - 'engine.datasource.syncTitle': '从“{name}”同步对象', - 'engine.datasource.syncDescription': '选择要作为对象导入的远程表。每张表都会成为一个对象定义。', - 'engine.datasource.introspecting': '正在读取远程表…', - 'engine.datasource.noRemoteTables': '此数据源上没有找到远程表。', - 'engine.datasource.columnCount': '{count} 列', - 'engine.datasource.createObjectsNone': '创建对象', - 'engine.datasource.createObjectsOne': '创建 1 个对象', - 'engine.datasource.createObjects': '创建 {count} 个对象', - 'engine.datasource.failed': '失败', - 'engine.datasource.rowTestOk': '{name}:连接正常', - 'engine.datasource.rowTestOkLatency': '{name}:连接正常({latency} 毫秒)', - 'engine.datasource.loadFailed': '加载数据源失败:{error}', - 'engine.datasource.loadDriversFailed': '加载驱动失败:{error}', - 'engine.datasource.openFailed': '打开 {name} 失败:{error}', - 'engine.datasource.deleted': '已删除 {name}。', - 'engine.datasource.deleteFailed': '删除 {name} 失败:{error}', - 'engine.datasource.created': '已创建 {name}。', - 'engine.datasource.updated': '已更新 {name}。', - 'engine.datasource.saveFailed': '保存失败:{error}', - 'engine.datasource.introspectFailed': '读取 {name} 的远程表失败:{error}', - 'engine.datasource.syncedOne': '已从 {name} 同步 1 个对象。', - 'engine.datasource.synced': '已从 {name} 同步 {count} 个对象。', - 'engine.datasource.syncFailed': '失败:{failures}', 'engine.typeAction.confirmTitle': '对“{item}”执行{action}?', 'engine.typeAction.confirmTitleNoItem': '执行{action}?', 'engine.layers.code': '代码', @@ -2940,16 +2218,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.docPreview.source': 'Markdown 源码', 'engine.docPreview.sourceLocale': 'Markdown 源码({locale})', 'engine.docPreview.sourcePlaceholder': '# 标题\n\n在此编写 Markdown…', - 'engine.datasourcePreview.empty': '设置名称并至少指定一个驱动,即可查看数据源预览。', - 'engine.datasourcePreview.unknownDriver': '未知', - 'engine.datasourcePreview.active': '已启用', - 'engine.datasourcePreview.disabled': '已停用', - 'engine.datasourcePreview.connection': '连接', - 'engine.datasourcePreview.noConfig': '未设置任何配置项。', - 'engine.datasourcePreview.redacted': '已隐去', - 'engine.datasourcePreview.pool': '连接池', - 'engine.datasourcePreview.notConfigured': '未配置', - 'engine.datasourcePreview.keyCount': '{{count} 个键}', 'engine.emailTemplatePreview.empty': '在“表单”标签页中填写主题 / 正文,即可查看预览。', 'engine.emailTemplatePreview.emptyBody': '(正文为空)', 'engine.emailTemplatePreview.from': '发件人', @@ -3180,59 +2448,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.fieldsListEditor.empty': '还没有列。请在下方添加字段。', 'engine.fieldsListEditor.colN': '列 {n}', 'engine.viewColumnPanes.remove': '移除 {label}', - 'engine.externalDatasource.unavailable': '此服务器未启用数据联邦。', - 'engine.externalDatasource.saveFirst': '请先保存数据源,才能浏览其远程表并校验联邦对象。', - 'engine.externalDatasource.title': '外部数据源', - 'engine.externalDatasource.writesAllowed': '允许写入', - 'engine.externalDatasource.readOnly': '只读', - 'engine.externalDatasource.snapshot': '快照 {time}', - 'engine.externalDatasource.refresh': '刷新目录', - 'engine.externalDatasource.tables': '表', - 'engine.externalDatasource.validation': '校验', - // objectui#10862, slice 4 — SchemaBrowser, ValidationPanel, ImportObjectDialog. - 'engine.externalDatasource.browser.filter': '筛选表…', - 'engine.externalDatasource.browser.refresh': '刷新', - 'engine.externalDatasource.browser.introspecting': '正在读取远程表结构…', - 'engine.externalDatasource.browser.noRemoteTables': '未找到远程表(请检查数据源的 allowedSchemas)。', - 'engine.externalDatasource.browser.noMatch': '没有与筛选条件匹配的表。', - 'engine.externalDatasource.browser.colTable': '表名', - 'engine.externalDatasource.browser.colColumns': '列', - 'engine.externalDatasource.browser.colRows': '行数(估算)', - 'engine.externalDatasource.browser.import': '导入', - 'engine.externalDatasource.browser.unavailable': - '此服务器未启用数据联邦。必须注册 {service} 服务,才能浏览表、生成草稿并进行校验。', - 'engine.externalDatasource.check.unavailable': '此服务器未启用数据联邦,因此无法进行校验。', - 'engine.externalDatasource.check.intro': '检查 {datasource} 上的每个联邦对象是否仍与其实时远程表一致。', - 'engine.externalDatasource.check.run': '运行校验', - 'engine.externalDatasource.check.allMatchOne': '全部 {count} 个对象均与远程表结构一致。', - 'engine.externalDatasource.check.allMatchOther': '全部 {count} 个对象均与远程表结构一致。', - 'engine.externalDatasource.check.divergeOne': '{count} 个对象中有 {diverged} 个与远程表结构不一致。', - 'engine.externalDatasource.check.divergeOther': '{count} 个对象中有 {diverged} 个与远程表结构不一致。', - 'engine.externalDatasource.check.noObjects': '尚无联邦对象绑定到此数据源。请在“表”标签页中导入一张表以开始。', - 'engine.externalDatasource.check.diffsOne': '{count} 处差异', - 'engine.externalDatasource.check.diffsOther': '{count} 处差异', - 'engine.externalDatasource.check.expectedActual': '预期 {expected},实际 {actual}', - 'engine.externalDatasource.diff.missingTable': '缺少表', - 'engine.externalDatasource.diff.missingColumn': '缺少列', - 'engine.externalDatasource.diff.typeMismatch': '类型不匹配', - 'engine.externalDatasource.diff.nullabilityMismatch': '可空性不匹配', - 'engine.externalDatasource.diff.unmappedColumn': '未映射的列', - 'engine.externalDatasource.diff.pkMismatch': '主键不匹配', - 'engine.externalDatasource.diff.indexMismatch': '索引不匹配', - 'engine.externalDatasource.diff.unmappedIndex': '未映射的索引', - 'engine.externalDatasource.diff.defaultMismatch': '列默认值不匹配', - 'engine.externalDatasource.diff.unreachable': '未检查 —— 远程不可达', - 'engine.externalDatasource.import.title': '导入为对象', - 'engine.externalDatasource.import.intro': - '将远程表 {table} 映射为绑定到 {datasource} 的 ObjectStack 对象。远程表结构不会被修改。', - 'engine.externalDatasource.import.generating': '正在生成草稿…', - 'engine.externalDatasource.import.done': '已导入对象 {name}。', - 'engine.externalDatasource.import.doneNext': '请复查其绑定并运行校验,确认它与远程表一致。', - 'engine.externalDatasource.import.objectName': '对象名称', - 'engine.externalDatasource.import.reviewOne': '{count} 个列需要复查', - 'engine.externalDatasource.import.reviewOther': '{count} 个列需要复查', - 'engine.externalDatasource.import.source': '生成的源码', - 'engine.externalDatasource.import.importing': '正在导入…', // 连线(edge)Type 枚举值 —— 属性表单里的 SchemaForm 下拉(与画布连线检查器一致)。 'engine.enum.type.default': '普通', 'engine.enum.type.conditional': '条件', @@ -3352,22 +2567,13 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.draftConflict.overwriteConfirm': '覆盖', 'engine.draftConflict.back': '返回', 'engine.draftConflict.notSaved': '未保存:{type}「{name}」在你打开后已在别处保存。', - 'engine.studio.data.tab.advanced': '高级', // Standard create-dialog field labels (shared by object / app / flow / permission). 'engine.studio.app.nameLabel': '应用名称', 'engine.studio.app.idLabel': '标识', - 'engine.studio.data.nameLabel': '对象名称', - 'engine.studio.data.idLabel': '标识', - 'engine.studio.auto.nameLabel': '流程名称', - 'engine.studio.auto.idLabel': '标识', - 'engine.studio.access.nameLabel': '名称', - 'engine.studio.access.idLabel': '标识', 'engine.studio.pkg.manage': '软件包信息与设置', 'engine.studio.pkg.manageMissing': '软件包 {id} 不在已安装列表中 —— 可能已在别处被删除或卸载。', 'engine.studio.pkg.manageRefreshFailed': '软件包 {id} 在该操作后未能刷新 —— 已关闭面板,以免把操作前的记录当作最新呈现。{error}', - 'engine.studio.data.savedDraft': '对象「{label}」已存为草稿', - 'engine.studio.data.lastSaved': '已于 {time} 保存', 'engine.studio.publish': '发布', 'engine.studio.loading': '加载中…', 'engine.studio.loadFailed': '加载失败', @@ -3406,115 +2612,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.toggleRail': '切换侧栏', 'engine.studio.landingHome': 'Studio 首页', 'engine.studio.desktopHint': 'Studio 专为桌面屏幕设计。', - // Pillar tab labels - 'engine.fieldType.text': '单行文本', - 'engine.fieldType.textarea': '多行文本', - 'engine.fieldType.email': '邮箱', - 'engine.fieldType.url': '网址', - 'engine.fieldType.phone': '电话', - 'engine.fieldType.password': '密码', - 'engine.fieldType.secret': '密钥', - 'engine.fieldType.markdown': 'Markdown', - 'engine.fieldType.html': 'HTML', - 'engine.fieldType.richtext': '富文本', - 'engine.fieldType.number': '数字', - 'engine.fieldType.currency': '货币', - 'engine.fieldType.percent': '百分比', - 'engine.fieldType.date': '日期', - 'engine.fieldType.datetime': '日期时间', - 'engine.fieldType.time': '时间', - 'engine.fieldType.boolean': '复选框', - 'engine.fieldType.toggle': '开关', - 'engine.fieldType.select': '下拉选择', - 'engine.fieldType.multiselect': '多选', - 'engine.fieldType.radio': '单选', - 'engine.fieldType.checkboxes': '复选组', - 'engine.fieldType.lookup': '查找关系', - 'engine.fieldType.master_detail': '主从关系', - 'engine.fieldType.tree': '树形关系', - 'engine.fieldType.user': '用户', - 'engine.fieldType.image': '图片', - 'engine.fieldType.file': '文件', - 'engine.fieldType.avatar': '头像', - 'engine.fieldType.video': '视频', - 'engine.fieldType.audio': '音频', - 'engine.fieldType.formula': '公式', - 'engine.fieldType.summary': '汇总', - 'engine.fieldType.autonumber': '自动编号', - 'engine.fieldType.composite': '复合字段', - 'engine.fieldType.repeater': '重复字段', - 'engine.fieldType.record': '命名分组', - 'engine.fieldType.location': '地理坐标', - 'engine.fieldType.address': '地址', - 'engine.fieldType.code': '代码', - 'engine.fieldType.json': 'JSON', - 'engine.fieldType.color': '颜色', - 'engine.fieldType.rating': '评分', - 'engine.fieldType.slider': '滑块', - 'engine.fieldType.signature': '签名', - 'engine.fieldType.qrcode': '二维码', - 'engine.fieldType.progress': '进度条', - 'engine.fieldType.tags': '标签', - 'engine.fieldType.vector': '向量', - // objectui#11793. - 'engine.fieldTypeDesc.text': '单行文本。', - 'engine.fieldTypeDesc.textarea': '多行纯文本。', - 'engine.fieldTypeDesc.email': '电子邮箱地址。', - 'engine.fieldTypeDesc.url': '网页地址。', - 'engine.fieldTypeDesc.phone': '电话号码。', - 'engine.fieldTypeDesc.password': '读取时会被遮蔽的文本。', - 'engine.fieldTypeDesc.secret': '加密存储的凭据(如 API 密钥),读取时会被遮蔽。', - 'engine.fieldTypeDesc.markdown': '用 Markdown 书写的格式化文本。', - 'engine.fieldTypeDesc.html': 'HTML 标记。', - 'engine.fieldTypeDesc.richtext': '可视化编辑的格式化文本。', - 'engine.fieldTypeDesc.number': '数字,整数或小数。', - 'engine.fieldTypeDesc.currency': '金额。', - 'engine.fieldTypeDesc.percent': '百分比。', - 'engine.fieldTypeDesc.date': '日历日期。', - 'engine.fieldTypeDesc.datetime': '带时间的日期。', - 'engine.fieldTypeDesc.time': '一天中的时间。', - 'engine.fieldTypeDesc.boolean': '是或否,显示为复选框。', - 'engine.fieldTypeDesc.toggle': '是或否,显示为开关。', - 'engine.fieldTypeDesc.select': '从选项列表中选一个值。', - 'engine.fieldTypeDesc.multiselect': '从选项列表中选多个值。', - 'engine.fieldTypeDesc.radio': '选一个值,显示为单选按钮。', - 'engine.fieldTypeDesc.checkboxes': '选多个值,显示为复选框。', - 'engine.fieldTypeDesc.lookup': '关联到另一个对象的一条记录。', - 'engine.fieldTypeDesc.master_detail': '关联到拥有本记录的父记录。', - 'engine.fieldTypeDesc.tree': '关联到同一对象中的父记录。', - 'engine.fieldTypeDesc.user': '关联到一位用户,如负责人或处理人。', - 'engine.fieldTypeDesc.image': '上传的图片。', - 'engine.fieldTypeDesc.file': '上传的文件。', - 'engine.fieldTypeDesc.avatar': '头像图片。', - 'engine.fieldTypeDesc.video': '上传的视频。', - 'engine.fieldTypeDesc.audio': '上传的音频。', - 'engine.fieldTypeDesc.formula': '由公式计算得出的值。', - 'engine.fieldTypeDesc.summary': '汇总相关记录得出的值。', - 'engine.fieldTypeDesc.autonumber': '为每条新记录分配的编号。', - 'engine.fieldTypeDesc.composite': '一组存放在一起的子字段。', - 'engine.fieldTypeDesc.repeater': '可重复的一组子字段列表。', - 'engine.fieldTypeDesc.record': '多组子字段,每组以自己的名称存放。', - 'engine.fieldTypeDesc.location': 'GPS 坐标。', - 'engine.fieldTypeDesc.address': '结构化的邮寄地址。', - 'engine.fieldTypeDesc.code': 'JSON、SQL 或 JavaScript 等源代码。', - 'engine.fieldTypeDesc.json': '结构化的 JSON 数据。', - 'engine.fieldTypeDesc.color': '颜色。', - 'engine.fieldTypeDesc.rating': '星级评分。', - 'engine.fieldTypeDesc.slider': '用滑块选取的数字。', - 'engine.fieldTypeDesc.signature': '电子签名。', - 'engine.fieldTypeDesc.qrcode': '二维码或条形码。', - 'engine.fieldTypeDesc.progress': '以进度条显示的值。', - 'engine.fieldTypeDesc.tags': '简单的标签列表。', - 'engine.fieldTypeDesc.vector': '用于 AI 语义搜索的向量嵌入。', - 'engine.fieldCategory.text': '文本', - 'engine.fieldCategory.number': '数值', - 'engine.fieldCategory.date': '日期/时间', - 'engine.fieldCategory.logic': '逻辑', - 'engine.fieldCategory.selection': '选择', - 'engine.fieldCategory.relation': '关系', - 'engine.fieldCategory.media': '媒体', - 'engine.fieldCategory.calculated': '计算', - 'engine.fieldCategory.advanced': '高级', 'engine.studio.aiCopilot': 'AI 副驾', // objectui#12725 — Studio 右栏:属性或 AI 副驾二选一。 'engine.studio.slot.label': '右栏', @@ -3581,7 +2678,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.org.openSetup': '在设置中打开', 'engine.studio.publishedAllFlows': '已发布 {count} 个不属于软件包的流程草稿', 'engine.studio.publishedAllFlowsOne': '已发布 {count} 个不属于软件包的流程草稿', - 'engine.studio.auto.deepLinkMissing': '链接指向的流程「{name}」不在这里。', 'engine.studio.designer.search': '搜索…', 'engine.studio.designer.select': '请选择…', 'engine.studio.designer.pickDate': '选择日期…', @@ -3665,57 +2761,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.rules.preset.rejectWhenHint': '自行搭建条件,无需写代码。', 'engine.studio.rules.preset.usesOne': '使用「{field}」。', 'engine.studio.rules.preset.usesTwo': '使用「{start}」和「{end}」。', - 'engine.studio.settings.basics': '基础信息', - 'engine.studio.settings.noInspector': '未注册对象默认检查器。', - 'engine.studio.settings.semanticRoles': '语义角色', - 'engine.studio.settings.semanticHint': '跨表单 / 列表 / 详情统一生效(ADR-0085)', - 'engine.studio.settings.nameField': '记录名称字段(nameField)—— 标题、链接、引用处显示的字段', - 'engine.studio.settings.autoDerive': '(自动推导)', - 'engine.studio.settings.stageField': '生命周期字段(stageField)—— 详情页顶部进度条按它的选项渲染', - 'engine.studio.settings.autoDetect': '(自动探测 status/stage 等字段名)', - 'engine.studio.settings.stageNone': '无 —— 这个对象没有线性流程,不显示进度条', - 'engine.studio.settings.highlightFields': '重点字段(highlightFields)—— 本对象最重要的字段,顺序即展示顺序;需要概览展示一条记录的界面都从中取用,各自能放下多少取多少', - 'engine.studio.settings.removeField': '移除 {field}', - 'engine.studio.settings.addFieldOption': '+ 添加字段…', - 'engine.studio.settings.undeclared': '(未声明 —— 各处按启发式自动挑选)', - 'engine.studio.settings.sharing': '记录共享模型(OWD)', - 'engine.studio.settings.sharingHint': '在岗位与共享规则之前生效的记录可见性基线(ADR-0056/0090)', - 'engine.studio.settings.sharingModel': '共享模型(sharingModel)—— 谁能查看和编辑他人拥有的记录', - 'engine.studio.settings.sharingUnset': '(未设置 —— 发布会被拒绝)', - 'engine.studio.settings.sharingPrivate': 'Private 私有 —— 仅记录所有者', - 'engine.studio.settings.sharingPublicRead': 'Public read 公共只读 —— 所有人可读,仅所有者可写', - 'engine.studio.settings.sharingPublicReadWrite': 'Public read/write 公共读写 —— 所有人可读可写', - 'engine.studio.settings.sharingControlledByParent': 'Controlled by parent 受父级控制 —— 继承自主记录', - 'engine.studio.settings.sharingDescUnset': - '未设置 —— 该对象无法发布(security-owd-unset)。运行时确实会回落为 Private(ADR-0090 D1),但这条基线必须是显式作出的决定,而不是疏漏。请选择一个模型 —— 选 Private 的行为完全相同,区别在于它是有意选定的。', - 'engine.studio.settings.sharingDescPrivate': - '只有所有者(以及经岗位或共享规则授予的用户)能访问记录。此时读取 / 编辑权限仅作用于自己拥有的记录。', - 'engine.studio.settings.sharingDescPublicRead': - '租户内每个用户都能读取所有记录;仅所有者可编辑。', - 'engine.studio.settings.sharingDescPublicReadWrite': - '租户内每个用户都能读取并编辑所有记录。', - 'engine.studio.settings.sharingDescControlledByParent': - '记录可见性通过主从(master-detail)关系继承自父级(主)记录。', - 'engine.studio.settings.capabilities': '能力开关(enable.*)', - 'engine.studio.settings.capabilitiesHint': '记录页能力 —— 这里的每个开关都由运行时真实强制', - 'engine.studio.settings.capTrackHistory': '历史标签页(trackHistory)', - 'engine.studio.settings.capTrackHistoryDesc': - '在记录页显示只读的历史标签页(审计轨迹)。配合字段级历史跟踪可得到可读的字段级差异;审计采集本身始终开启(合规要求)。', - 'engine.studio.settings.capFiles': '附件(files)', - 'engine.studio.settings.capFilesDesc': - '记录页通用附件面板(上传/下载/删除)。关闭后服务端同时拒绝指向该对象的新附件。', - 'engine.studio.settings.capFeeds': '评论与动态(feeds)', - 'engine.studio.settings.capFeedsDesc': - '记录讨论面板(支持 @提及 与表情回应)。关闭后面板隐藏,且服务端拒绝新评论。', - 'engine.studio.settings.capActivities': '活动时间线(activities)', - 'engine.studio.settings.capActivitiesDesc': - '将创建/更新/删除镜像到记录时间线。关闭后该对象不再产生时间线记录(合规审计日志不受影响)。', - 'engine.studio.settings.capSearchable': '全局搜索(searchable)', - 'engine.studio.settings.capSearchableDesc': - '将该对象的记录纳入跨对象全局搜索。关闭后记录不再出现在全局搜索结果中。匹配哪些字段由对象的 searchableFields 配置,与此开关无关。', - 'engine.studio.settings.capClone': '克隆(clone)', - 'engine.studio.settings.capCloneDesc': - '允许从记录页/API 复制记录。关闭后克隆接口对该对象返回 403。', 'engine.studio.pkg.writable': '可写', 'engine.studio.pkg.heading': '软件包(应用)', 'engine.studio.pkg.none': '暂无应用软件包', @@ -3730,66 +2775,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.nav.boundHint': '这个菜单项会打开该对象的记录列表。', 'engine.studio.nav.unboundHint': '选择一个对象,菜单项将打开它的记录列表。', 'engine.studio.nav.noObjects': '这个软件包还没有对象 — 请先在「数据」中创建一个。', - // Interfaces pillar - 'engine.studio.if.pickLeft': '从左侧选择一个菜单项', - 'engine.studio.if.internalId': '内部标识', - 'engine.studio.if.navHeading': '{app} · 导航', - 'engine.studio.if.editNavTitle': '编辑导航(拖拽排序 / 重命名 / 增删)', - 'engine.studio.if.doneEditTitle': '完成编辑', - 'engine.studio.if.noApp': '这个软件包还没有应用。', - 'engine.studio.if.noNavItems': '还没有导航项 —(点上方「编辑」添加)', - 'engine.studio.if.previewIsRuntime': '实时预览', - 'engine.studio.if.modeDesign': '设计', - 'engine.studio.if.modeRun': '运行', - 'engine.studio.if.modeSourcePage': '此处运行与设计显示相同:以 {kind} 编写的页面没有可隐藏的设计器元素。', - 'engine.studio.if.pageSaved': '已保存', - 'engine.studio.if.pageSavedAt': '已于 {time} 保存', - 'engine.studio.if.pageUnsaved': '未保存', - 'engine.studio.if.pageProps.label': '标签', - 'engine.studio.if.pageProps.identifier': '标识', - 'engine.studio.if.pageProps.kindFixed': '创建页面时选定,此处不可更改。', - 'engine.studio.if.pageProps.navEntries': '导航入口', - 'engine.studio.if.pageProps.noNavEntries': '没有导航入口打开此页面。', - 'engine.studio.if.tabCanvas': '画布', - 'engine.studio.if.noAppTitle': '这个软件包还没有应用', - 'engine.studio.if.noAppHint': '创建一个应用来设计它的导航与界面。', - 'engine.studio.if.objectUnpublishedTitle': '这个对象还没有发布', - 'engine.studio.if.objectUnpublishedHint': '发布软件包后,这里会显示它的记录。', - 'engine.studio.if.designersMissing': '本次会话没有注册任何元数据设计器,因此无法在这里预览或设计 {type}。', - 'engine.studio.if.noDesigner': '没有为 {type} 注册设计器,因此无法在这里预览或设计。', - 'engine.studio.if.objectHintPre': '运行态列表预览 · 改字段 / 结构请到「', - 'engine.studio.if.objectHintPost': '」', - 'engine.studio.if.kind.view': '视图', - 'engine.studio.if.kind.url': '链接', - 'engine.studio.if.kind.action': '动作', - 'engine.studio.if.kind.component': '组件', - 'engine.studio.if.kind.doc': '文档', - 'engine.studio.if.kind.group': '分组', - 'engine.studio.if.kind.separator': '分隔线', - 'engine.studio.if.kindDesc.object': '一个列表视图或一条记录', - 'engine.studio.if.kindDesc.page': '由区块搭建的自定义页面', - 'engine.studio.if.kindDesc.dashboard': '带筛选的部件看板', - 'engine.studio.if.kindDesc.report': '一份已保存的报表', - 'engine.studio.if.kindDesc.url': '外部链接,在当前或新标签页打开', - 'engine.studio.if.kindDesc.action': '从菜单运行一个全局动作', - 'engine.studio.if.kindDesc.component': '一个已注册的组件,例如审批收件箱', - 'engine.studio.if.kindDesc.doc': '一个文档页、一本文档册,或两者', - 'engine.studio.if.kindDesc.group': '菜单中带标题的一个分区', - 'engine.studio.if.kindDesc.separator': '一条分隔线,无标签,不打开任何内容', - 'engine.studio.if.addMenuTitle': '添加到导航', - 'engine.studio.if.emptyGroup': '这个软件包里没有条目', - 'engine.studio.if.openInData': '在「数据」中打开', - // objectui#12725 — 画布上方的状态行,以及条目的 ⋯ 菜单。 - 'engine.studio.if.status.design': '设计态', - 'engine.studio.if.status.run': '运行态预览', - 'engine.studio.if.status.unpublished': '含未发布改动', - 'engine.studio.if.canvasHint.pick': '点元素 → 右栏切到属性', - 'engine.studio.if.canvasHint.selected': '已选中:{label} · Esc 取消选中', - 'engine.studio.if.more': '更多', - 'engine.studio.if.viewDefinition': '查看定义', - 'engine.studio.if.copyJson': '复制 JSON', - 'engine.studio.if.copyFailed': '无法复制到剪贴板', - 'engine.studio.if.definitionTitle': '{label} · 定义', 'engine.studio.nav.eyebrow': '导航项 · {kind}', 'engine.studio.nav.icon': '图标', 'engine.studio.nav.visibleWhen': '可见条件', @@ -3876,33 +2861,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.interfaces.create.pageStarter': '新页面。请在源码编辑器中编写它的内容。', 'engine.studio.inspector.designersMissing': '本次会话没有注册任何元数据设计器,这里没有可编辑的内容。', 'engine.studio.inspector.noPageSchema': '页面设置不可用——无法加载页面 schema。', - // Data pillar - 'engine.studio.data.newFieldLabel': '新字段', - 'engine.studio.data.nameFieldLabel': '名称', - 'engine.studio.data.idExists': '标识符 "{name}" 已存在', - 'engine.studio.data.fieldCount': '{count} 字段', - 'engine.studio.data.pickObject': '选择一个对象', - 'engine.studio.data.objects': '对象', - 'engine.studio.data.searchObjects': '搜索对象…', - 'engine.studio.data.noObjects': '还没有对象 — 点上方的「+」新建', - 'engine.studio.data.labelPlaceholder': '显示名(如:报修工单)', - 'engine.studio.data.idPlaceholder': '标识符(如:repair_ticket)', - // [`7a90afdf9`] 新建对象对话框的第三项。此前只问两项,产出的对象会被发布门拒绝。 - 'engine.studio.data.owdLabel': '记录共享模型(OWD)—— 谁能看到他人拥有的记录', - 'engine.studio.data.owdHint': - '发布必填,之后可在“设置 → 记录共享模型”中修改。Private 与未设置时运行时的实际行为一致。', - 'engine.studio.data.newObject': '新建对象', - 'engine.studio.data.readOnlyPackage': '此包为只读——请切换到或新建一个可写的包后再添加对象', - 'engine.studio.data.firstObjectTitle': '从第一个对象开始', - 'engine.studio.data.firstObjectHint': - '对象是应用的数据基座(如「订单」「客户」)。点击下方按钮创建一个;之后为它设计字段、表单与自动化,最后一次发布。', - 'engine.studio.data.tab.records': '记录', - 'engine.studio.data.tab.form': '表单', - 'engine.studio.data.tab.rules': '验证', - 'engine.studio.data.tab.settings': '设置', - 'engine.studio.data.tab.hooks': '钩子', - 'engine.studio.data.tab.actions': '操作', - 'engine.studio.data.tab.api': 'API', // Actions view 'engine.studio.actions.none': '该对象没有操作。', 'engine.studio.actions.newButton': '新建操作', @@ -3938,29 +2896,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.hooks.async': '异步', 'engine.studio.hooks.pick': '选择一个钩子进行编辑。', 'engine.studio.hooks.newLabel': '新钩子', - 'engine.studio.data.addField': '添加字段', - 'engine.studio.data.addFieldCreate': '创建', - 'engine.studio.data.addFieldApiNameNotDerived': '标签里没有可用来生成 API 名称的英文字母,请手动填写(小写字母、数字和下划线)。', - 'engine.studio.data.addFieldApiNameTaken': '已有字段名为 {name},请填写其他 API 名称。', - 'engine.studio.data.editFieldProps': '编辑字段属性', - 'engine.studio.data.draftObjectTitle': '未发布的新对象', - 'engine.studio.data.draftObjectHint': - '「记录」网格查询真实数据,而这个对象发布前还没有数据表。请先在「表单 · 布局」里设计字段与分组,然后点顶栏「发布」— 发布后这里就是它的实时数据网格。', - 'engine.studio.data.goDesignFields': '去「表单 · 布局」设计字段', - 'engine.studio.data.form.layout': '布局', - 'engine.studio.data.form.preview': '预览', - 'engine.studio.data.form.layoutBadge': '草稿布局 · 含未保存改动', - 'engine.studio.data.form.layoutBadgeClean': '草稿布局', - 'engine.studio.data.form.layoutBadgeReadOnly': '只读布局 · 本软件包不可编辑', - 'engine.studio.data.form.previewBadge': '按应用中的样子预览表单', - 'engine.studio.data.form.draftChip': '草稿预览', - 'engine.studio.data.form.draftChipTitle': '此预览渲染草稿定义,含未保存的改动。这里不会保存任何内容。', - 'engine.studio.data.form.sourceLabel': '预览来源', - 'engine.studio.data.form.sourceDraft': '草稿', - 'engine.studio.data.form.sourcePublished': '已发布', - 'engine.studio.data.fieldProps': '字段属性', - 'engine.studio.data.groupProps': '分组属性', - 'engine.studio.data.fieldInspectorMissing': '本次会话没有注册字段检查器,因此无法在这里编辑该字段的属性。', 'engine.studio.designer.group.kind': '分组', 'engine.studio.designer.group.settings': '分组设置', 'engine.studio.designer.group.nameLabel': '分组名称', @@ -3970,107 +2905,8 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.designer.group.collapseExpanded': '可折叠 · 默认展开', 'engine.studio.designer.group.collapseCollapsed': '可折叠 · 默认折叠', 'engine.studio.designer.group.collapseHint': '控制该分组在真实表单(预览)中的折叠方式。', - // Automations pillar - 'engine.studio.auto.nodeStart': '开始', - 'engine.studio.auto.nodeEnd': '结束', - 'engine.studio.auto.savedDraft': '自动化「{label}」已存为草稿', - 'engine.studio.auto.defaultOff': '默认停用 · 审阅后再启用', - 'engine.studio.auto.heading': '自动化', - 'engine.studio.auto.on': '已启用', - 'engine.studio.auto.off': '已停用', - 'engine.studio.auto.offTitle': '已停用 — 不会运行', - 'engine.studio.auto.onBound': '已启用 · 已绑定触发器', - 'engine.studio.auto.onUnbound': '已启用 · 无触发器(手动运行)', - 'engine.studio.auto.notRunning': '未在此运行', - 'engine.studio.auto.notRunningTitle': '已启用,但它的触发器在此部署中未生效', - 'engine.studio.auto.unpublishedTitle': '尚未部署 — 发布后才会部署', - 'engine.studio.auto.refused': '引擎拒绝注册', - 'engine.studio.auto.unregisteredTitle': '此部署的自动化引擎未注册该流程', - 'engine.studio.auto.enabled': '已启用', - 'engine.studio.auto.disabled': '已停用', - 'engine.studio.auto.enableTitle': '启用此自动化(发布后生效)', - 'engine.studio.auto.disableTitle': '停用此自动化(发布后生效)', - 'engine.studio.auto.enabledToast': '已设为启用 — 发布后生效', - 'engine.studio.auto.disabledToast': '已设为停用 — 发布后生效', - 'engine.studio.auto.newTitle': '新建自动化', - 'engine.studio.auto.none': '还没有自动化 — 点上方的「+」新建', - 'engine.studio.auto.emptyMain': '这个软件包还没有自动化。', - 'engine.studio.auto.namePlaceholder': '名称(如:录用通知)', - 'engine.studio.auto.idPlaceholder': '标识符(如:offer_notice)', // objectui#11788 — 新建自动化对话框里不选触发方式时的选项。 'engine.studio.newAutoTrigger.later': '稍后在开始节点上选择', - 'engine.studio.auto.presets': '何时运行', - 'engine.studio.auto.preset.created': '新建记录时', - 'engine.studio.auto.preset.updated': '更新记录时', - 'engine.studio.auto.preset.deleted': '删除记录时', - 'engine.studio.auto.preset.manual': '由按钮或其他自动化启动时', - 'engine.studio.auto.preset.needsObject': '请选择它监视的对象。', - 'engine.studio.auto.canvasHint': '点选画布上的节点即可配置', - 'engine.studio.auto.config': '配置', - 'engine.studio.auto.emptyLine1': '在画布里点选一个节点,', - 'engine.studio.auto.emptyLine2': '它的配置会在这里显示。', - 'engine.studio.auto.designersMissing': '本次会话没有注册任何元数据设计器,因此无法在这里设计该流程。', - 'engine.studio.auto.group.noObject': '无对象', - 'engine.studio.auto.kind.record': '记录触发', - 'engine.studio.auto.kind.scheduled': '定时', - 'engine.studio.auto.kind.screen': '交互页面', - 'engine.studio.auto.kind.autolaunched': '自动启动', - 'engine.studio.auto.legend': '图例', - // Access pillar - 'engine.studio.access.created': '权限集「{label}」已创建', - 'engine.studio.access.title': '权限矩阵', - 'engine.studio.access.subtitle': '对象 × CRUD · 字段级读写', - 'engine.studio.access.bannerTitle': - '此矩阵仅列出本包声明的对象,「Save」只合并本包切片 —— 其他包贡献的授权原样保留。编辑保存为软件包草稿,点击顶栏「发布」后随整个包一起生效(与数据、界面一致)。', - 'engine.studio.access.banner': '仅本包对象 · 保存为草稿', - 'engine.studio.access.bannerTitleReadOnly': - '此矩阵仅列出本包声明的对象。本软件包为只读:其权限集可在此查看,但不能修改。', - // ADR-0090 D5/D9 — 待确认的受众绑定建议(isDefault 权限集,管理员确认后生效,服务端绝不自动绑定) - 'engine.studio.access.suggestPromptEveryone': '此包建议将「{set}」授予所有已登录用户(Everyone 岗位)。', - 'engine.studio.access.suggestPromptGuest': '此包建议将「{set}」授予未登录访客(Guest 岗位)。', - 'engine.studio.access.suggestConfirm': '授予', - 'engine.studio.access.suggestConfirming': '授予中…', - 'engine.studio.access.suggestDismiss': '忽略', - 'engine.studio.access.suggestConfirmedToast': '「{set}」已授予 {anchor}。', - 'engine.studio.access.suggestDismissedToast': '已忽略「{set}」的建议。', - // objectstack#22801 → A — accepted / not-in-force / dismissed states and revoke. - 'engine.studio.access.suggestGuestUnsupported': '此处无法授予:Guest 岗位的权限集不由已接受的建议派生。', - 'engine.studio.access.suggestAcceptedState': '「{set}」已为所有已登录用户(Everyone 岗位)接受。', - 'engine.studio.access.suggestNotInForceState': '「{set}」已接受,但尚未生效:本次请求未改变 Everyone 岗位。原因见服务器日志。', - 'engine.studio.access.suggestDismissedState': '「{set}」未授予:该建议已被忽略或撤销。', - 'engine.studio.access.suggestRevoke': '撤销', - 'engine.studio.access.suggestRevoking': '撤销中…', - 'engine.studio.access.suggestRevokedToast': '已撤销对「{set}」的接受。', - 'engine.studio.access.heading': '权限集', - 'engine.studio.access.search': '搜索权限…', - 'engine.studio.access.none': '还没有权限集 — 点上方的「+」新建', - 'engine.studio.access.labelPlaceholder': '显示名(如:销售权限)', - 'engine.studio.access.idPlaceholder': '标识符(如:sales_perms)', - 'engine.studio.access.new': '新建权限集', - 'engine.studio.access.emptyMain': '这个软件包还没有权限集。', - 'engine.studio.access.tab.objects': '对象', - 'engine.studio.access.tab.capabilities': '系统能力', - 'engine.studio.access.tab.rls': '行级安全', - 'engine.studio.access.tab.tabs': '标签页可见性', - 'engine.studio.access.tab.admin': '委派管理', - // zh has one plural form, so each `…One` / `…Other` pair carries the same row. - 'engine.studio.access.grantedOne': '已授权 {count} 个对象', - 'engine.studio.access.grantedOther': '已授权 {count} 个对象', - 'engine.studio.access.overridesOne': '{count} 个字段覆盖', - 'engine.studio.access.overridesOther': '{count} 个字段覆盖', - 'engine.studio.access.objectCountOne': '{count} 个对象', - 'engine.studio.access.objectCountOther': '{count} 个对象', - 'engine.studio.access.objectCountFiltered': '{count} 个对象中的 {shown} 个', - 'engine.studio.access.more': '更多操作', - 'engine.studio.access.colOwd': 'OWD', - 'engine.studio.access.legend.create': '创建', - 'engine.studio.access.legend.read': '读取', - 'engine.studio.access.legend.edit': '编辑', - 'engine.studio.access.legend.delete': '删除', - 'engine.studio.access.legend.transfer': '转移', - 'engine.studio.access.legend.viewAll': '查看全部', - 'engine.studio.access.legend.modifyAll': '修改全部', - 'engine.studio.access.bulkHint': '批量操作(R · CRUD · All · None)显示在指针所在或获得键盘焦点的行上。', // ── 记录共享基线(OWD)总览 (objectui#2505) ────────────────────────── 'engine.studio.owd.railLabel': '记录共享(OWD)', @@ -4091,89 +2927,6 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.owd.masterTip': '受父级控制 —— 记录可见性继承自主记录。', - // ── 访问解释面板(ADR-0090 D6 —「为什么该用户可以访问?」)──────────── - 'engine.studio.access.explain.open': '解释访问权限', - 'engine.studio.access.explain.title': '为什么该用户可以访问?', - 'engine.studio.access.explain.description': - '与运行时强制执行走同一条评估管线,逐层报告各层的贡献 —— 权限集 → 岗位 → 绑定链,以及 OWD/共享判定。', - 'engine.studio.access.explain.user': '用户', - 'engine.studio.access.explain.self': '我(当前调用者)', - 'engine.studio.access.explain.pickUser': '选择用户…', - 'engine.studio.access.explain.clearUser': '重置为我', - 'engine.studio.access.explain.pickUserTitle': '选择要解释的用户', - 'engine.studio.access.explain.object': '对象', - 'engine.studio.access.explain.objectPlaceholder': '对象 API 名称', - 'engine.studio.access.explain.objectSelect': '选择对象…', - 'engine.studio.access.explain.operation': '操作', - 'engine.studio.access.explain.run': '解释', - 'engine.studio.access.explain.running': '解释中…', - 'engine.studio.access.explain.allowedLine': '该主体对「{object}」执行 {operation} 将被【允许】。', - 'engine.studio.access.explain.deniedLine': '该主体对「{object}」执行 {operation} 将被【拒绝】。', - 'engine.studio.access.explain.principal': '主体', - 'engine.studio.access.explain.positions': '岗位', - 'engine.studio.access.explain.permissionSets': '权限集', - 'engine.studio.access.explain.layers': '评估管线', - 'engine.studio.access.explain.contributors': '贡献来源', - 'engine.studio.access.explain.readFilter': '合成的行过滤器(读取)', - 'engine.studio.access.explain.empty': '选择对象和操作,然后点击「解释」。', - 'engine.studio.access.explain.failed': '解释失败', - 'engine.studio.access.explain.forbidden': - '无法解释该用户 —— 需要 manage_users 能力,或一个覆盖该用户的委托管理范围(ADR-0090 D6/D12)。', - 'engine.studio.access.explain.layer.principal': '主体解析', - 'engine.studio.access.explain.layer.required_permissions': '前置能力', - 'engine.studio.access.explain.layer.object_crud': '对象 CRUD 授权', - 'engine.studio.access.explain.layer.fls': '字段级安全', - 'engine.studio.access.explain.layer.owd_baseline': '记录基线(OWD)', - 'engine.studio.access.explain.layer.depth': '范围深度', - 'engine.studio.access.explain.layer.sharing': '共享', - 'engine.studio.access.explain.layer.vama_bypass': '查看/修改全部数据旁路', - 'engine.studio.access.explain.layer.rls': '行级安全', - 'engine.studio.access.explain.verdict.grants': '授予', - 'engine.studio.access.explain.verdict.denies': '拒绝', - 'engine.studio.access.explain.verdict.narrows': '收窄', - 'engine.studio.access.explain.verdict.widens': '放宽', - 'engine.studio.access.explain.verdict.neutral': '中性', - 'engine.studio.access.explain.verdict.not_applicable': '不适用', - 'engine.studio.access.explain.op.read': '读取', - 'engine.studio.access.explain.op.create': '新建', - 'engine.studio.access.explain.op.update': '更新', - 'engine.studio.access.explain.op.delete': '删除', - 'engine.studio.access.explain.op.transfer': '转移', - 'engine.studio.access.explain.op.restore': '恢复', - 'engine.studio.access.explain.op.purge': '清除', - // [C2 / ADR-0095] 记录级解释 - 'engine.studio.access.explain.record': '记录(可选)', - 'engine.studio.access.explain.recordPlaceholder': '记录 id — 解释某一条具体记录', - 'engine.studio.access.explain.pickRecord': '选择', - 'engine.studio.access.explain.clearRecord': '清除记录', - 'engine.studio.access.explain.pickRecordTitle': '选择要解释的记录', - 'engine.studio.access.explain.recordVisibleLine': '记录 {recordId} 对该操作【可见】。', - 'engine.studio.access.explain.recordHiddenLine': '记录 {recordId} 对该操作【不可见】。', - 'engine.studio.access.explain.decidedBy': '决定层', - 'engine.studio.access.explain.postureHint': '该主体被评估时所处的 posture 档位(ADR-0095 D2)。', - 'engine.studio.access.explain.posture.PLATFORM_ADMIN': '平台管理员', - 'engine.studio.access.explain.posture.TENANT_ADMIN': '租户管理员', - 'engine.studio.access.explain.posture.MEMBER': '成员', - 'engine.studio.access.explain.posture.EXTERNAL': '外部用户', - 'engine.studio.access.explain.layer.tenant_isolation': '租户隔离(Layer 0)', - 'engine.studio.access.explain.kernelTier.layer_0_tenant': '租户墙', - 'engine.studio.access.explain.kernelTier.layer_1_business': '业务 RLS', - 'engine.studio.access.explain.kernelTierHint': 'Layer 0 = 永远最先的组织墙;Layer 1 = 业务行级安全。', - 'engine.studio.access.explain.recordOutcome': '该记录', - 'engine.studio.access.explain.outcome.admitted': '准入', - 'engine.studio.access.explain.outcome.excluded': '排除', - 'engine.studio.access.explain.outcome.not_evaluated': '未评估', - 'engine.studio.access.explain.matches': '记录匹配', - 'engine.studio.access.explain.noMatch': '记录不匹配', - 'engine.studio.access.explain.effectiveRowFilter': '有效行过滤', - 'engine.studio.access.explain.ruleKind.tenant_filter': '租户过滤', - 'engine.studio.access.explain.ruleKind.owd_baseline': 'OWD 基线', - 'engine.studio.access.explain.ruleKind.ownership': '归属', - 'engine.studio.access.explain.ruleKind.record_share': '记录共享', - 'engine.studio.access.explain.ruleKind.sharing_rule': '共享规则', - 'engine.studio.access.explain.ruleKind.team': '团队', - 'engine.studio.access.explain.ruleKind.territory': '区域', - 'engine.studio.access.explain.ruleKind.rls_policy': 'RLS 策略', // ── AppNavCanvas (nav-tree editor, used by Studio + AppPreview) ───────── 'engine.appNav.heading': '导航', diff --git a/ui/packages/i18n/src/namespaces/studio/en.ts b/ui/packages/i18n/src/namespaces/studio/en.ts index 58ef59fd94d..ce2ef3a260d 100644 --- a/ui/packages/i18n/src/namespaces/studio/en.ts +++ b/ui/packages/i18n/src/namespaces/studio/en.ts @@ -1759,6 +1759,674 @@ const STUDIO_EN: Record = { 'engine.packages.detail.adoptDescription': 'Every package-less (loose) metadata item in this system moves into this base. Items that already belong to a package stay where they are.', 'engine.packages.detail.adoptedOne': '{count} item adopted into this base.', 'engine.packages.detail.adoptedOther': '{count} items adopted into this base.', + // engine.studio.{access,if,data,auto,settings}, the data source pages + // (engine.datasource, engine.externalDatasource, engine.datasourcePreview) and + // the field types (engine.fieldType, engine.fieldTypeDesc, engine.fieldCategory) + // (#25922 slice S4). + // objectui#13333 — the datasource list's delete confirm and the type-level + // action confirm, both in-app dialogs naming what they act on. + 'engine.datasource.deleteTitle': 'Delete datasource "{item}"?', + 'engine.datasource.deleteBody': 'The connection {name} is removed. This cannot be undone.', + 'engine.datasource.delete': 'Delete', + // objectui#13460 — the datasource page's own copy. Author data (names, driver + // labels and descriptions, server messages) and the driver config schema's enum + // option values (objectui#13353) are not rows here. + 'engine.datasource.title': 'Datasources', + 'engine.datasource.subtitle': 'Connect external databases and sync their tables in as objects.', + 'engine.datasource.refresh': 'Refresh', + 'engine.datasource.new': 'New datasource', + 'engine.datasource.loading': 'Loading…', + 'engine.datasource.empty': 'No datasources yet. Click {action} to connect one.', + 'engine.datasource.test': 'Test', + 'engine.datasource.sync': 'Sync objects', + 'engine.datasource.edit': 'Edit', + 'engine.datasource.statusOk': 'connected', + 'engine.datasource.statusError': 'connect failed', + 'engine.datasource.statusBlocked': 'blocked by policy', + 'engine.datasource.statusUnvalidated': 'not connected', + 'engine.datasource.editTitle': 'Edit “{name}”', + 'engine.datasource.editDescription': 'Update the connection. Leave the credential blank to keep the current one.', + 'engine.datasource.newDescription': 'Connect an external database. Credentials are encrypted and never stored in metadata.', + 'engine.datasource.name': 'Name', + 'engine.datasource.nameInvalid': 'Lowercase letters, digits, and underscores; must not start with a digit.', + 'engine.datasource.label': 'Label', + 'engine.datasource.labelPlaceholder': 'Human-friendly name', + 'engine.datasource.driver': 'Driver', + 'engine.datasource.driverPlaceholder': 'Choose a driver', + 'engine.datasource.schemaMode': 'Schema mode', + 'engine.datasource.schemaModeManaged': 'Managed (ObjectStack owns the schema)', + 'engine.datasource.schemaModeExternal': 'External (read existing schema as-is)', + 'engine.datasource.schemaModeValidateOnly': 'Validate only', + 'engine.datasource.testConnection': 'Test connection', + 'engine.datasource.connectionFailed': 'Connection failed', + 'engine.datasource.connectionOk': 'Connection ok', + 'engine.datasource.connectionOkLatency': 'Connection ok ({latency}ms)', + 'engine.datasource.saveChanges': 'Save changes', + 'engine.datasource.create': 'Create datasource', + 'engine.datasource.syncTitle': 'Sync objects from “{name}”', + 'engine.datasource.syncDescription': 'Pick the remote tables to import as objects. Each becomes an object definition.', + 'engine.datasource.introspecting': 'Introspecting…', + 'engine.datasource.noRemoteTables': 'No remote tables found on this datasource.', + 'engine.datasource.columnCount': '{count} cols', + 'engine.datasource.createObjectsNone': 'Create objects', + 'engine.datasource.createObjectsOne': 'Create 1 object', + 'engine.datasource.createObjects': 'Create {count} objects', + 'engine.datasource.failed': 'failed', + 'engine.datasource.rowTestOk': '{name}: connection ok', + 'engine.datasource.rowTestOkLatency': '{name}: connection ok ({latency}ms)', + 'engine.datasource.loadFailed': 'Load datasources: {error}', + 'engine.datasource.loadDriversFailed': 'Load drivers: {error}', + 'engine.datasource.openFailed': 'Open {name}: {error}', + 'engine.datasource.deleted': 'Deleted {name}.', + 'engine.datasource.deleteFailed': 'Delete {name}: {error}', + 'engine.datasource.created': 'Created {name}.', + 'engine.datasource.updated': 'Updated {name}.', + 'engine.datasource.saveFailed': 'Save: {error}', + 'engine.datasource.introspectFailed': 'Introspect {name}: {error}', + 'engine.datasource.syncedOne': 'Synced 1 object from {name}.', + 'engine.datasource.synced': 'Synced {count} objects from {name}.', + 'engine.datasource.syncFailed': 'Failed: {failures}', + 'engine.datasourcePreview.empty': 'Set a name and at least a driver to see the datasource preview.', + 'engine.datasourcePreview.unknownDriver': 'unknown', + 'engine.datasourcePreview.active': 'Active', + 'engine.datasourcePreview.disabled': 'Disabled', + 'engine.datasourcePreview.connection': 'Connection', + 'engine.datasourcePreview.noConfig': 'No config keys set.', + 'engine.datasourcePreview.redacted': 'redacted', + 'engine.datasourcePreview.pool': 'Pool', + 'engine.datasourcePreview.notConfigured': 'not configured', + 'engine.datasourcePreview.keyCount': '{{count} keys}', + 'engine.externalDatasource.unavailable': 'Federation is not enabled on this server.', + 'engine.externalDatasource.saveFirst': + 'Save the datasource first to browse its remote tables and validate federated objects.', + 'engine.externalDatasource.title': 'External Datasource', + 'engine.externalDatasource.writesAllowed': 'writes allowed', + 'engine.externalDatasource.readOnly': 'read-only', + 'engine.externalDatasource.snapshot': 'snapshot {time}', + 'engine.externalDatasource.refresh': 'Refresh catalog', + 'engine.externalDatasource.tables': 'Tables', + 'engine.externalDatasource.validation': 'Validation', + // objectui#10862, slice 4 — the panel's two tabs and the import dialog: + // the tables tab (`SchemaBrowser`), the validation tab (`ValidationPanel`, + // one row per diff kind, total over the spec's kind union) and + // `ImportObjectDialog`. A `{hole}` a code span fills is rendered by + // `withNodes`. + 'engine.externalDatasource.browser.filter': 'Filter tables…', + 'engine.externalDatasource.browser.refresh': 'Refresh', + 'engine.externalDatasource.browser.introspecting': 'Introspecting remote schema…', + 'engine.externalDatasource.browser.noRemoteTables': + 'No remote tables found (check the datasource’s allowedSchemas).', + 'engine.externalDatasource.browser.noMatch': 'No tables match the filter.', + 'engine.externalDatasource.browser.colTable': 'Table', + 'engine.externalDatasource.browser.colColumns': 'Columns', + 'engine.externalDatasource.browser.colRows': 'Rows (est.)', + 'engine.externalDatasource.browser.import': 'Import', + 'engine.externalDatasource.browser.unavailable': + 'Federation is not enabled on this server. The {service} service must be registered for table browsing, drafting, and validation to work.', + 'engine.externalDatasource.check.unavailable': + 'Federation is not enabled on this server, so validation is unavailable.', + 'engine.externalDatasource.check.intro': + 'Check that every federated object on {datasource} still matches its live remote table.', + 'engine.externalDatasource.check.run': 'Run validation', + 'engine.externalDatasource.check.allMatchOne': 'All {count} object match the remote schema.', + 'engine.externalDatasource.check.allMatchOther': 'All {count} objects match the remote schema.', + 'engine.externalDatasource.check.divergeOne': '{diverged} of {count} object diverge from the remote schema.', + 'engine.externalDatasource.check.divergeOther': '{diverged} of {count} objects diverge from the remote schema.', + 'engine.externalDatasource.check.noObjects': + 'No federated objects are bound to this datasource yet. Import a table from the Tables tab to get started.', + 'engine.externalDatasource.check.diffsOne': '{count} diff', + 'engine.externalDatasource.check.diffsOther': '{count} diffs', + 'engine.externalDatasource.check.expectedActual': 'expected {expected}, actual {actual}', + 'engine.externalDatasource.diff.missingTable': 'Missing table', + 'engine.externalDatasource.diff.missingColumn': 'Missing column', + 'engine.externalDatasource.diff.typeMismatch': 'Type mismatch', + 'engine.externalDatasource.diff.nullabilityMismatch': 'Nullability mismatch', + 'engine.externalDatasource.diff.unmappedColumn': 'Unmapped column', + 'engine.externalDatasource.diff.pkMismatch': 'Primary-key mismatch', + 'engine.externalDatasource.diff.indexMismatch': 'Index mismatch', + 'engine.externalDatasource.diff.unmappedIndex': 'Unmapped index', + 'engine.externalDatasource.diff.defaultMismatch': 'Column default mismatch', + 'engine.externalDatasource.diff.unreachable': 'Not checked — remote unreachable', + 'engine.externalDatasource.import.title': 'Import as Object', + 'engine.externalDatasource.import.intro': + 'Map remote table {table} into an ObjectStack object bound to {datasource}. The remote schema is never modified.', + 'engine.externalDatasource.import.generating': 'Generating draft…', + 'engine.externalDatasource.import.done': 'Imported object {name}.', + 'engine.externalDatasource.import.doneNext': + 'Review its binding and run validation to confirm it matches the remote table.', + 'engine.externalDatasource.import.objectName': 'Object name', + 'engine.externalDatasource.import.reviewOne': '{count} column need review', + 'engine.externalDatasource.import.reviewOther': '{count} columns need review', + 'engine.externalDatasource.import.source': 'Generated source', + 'engine.externalDatasource.import.importing': 'Importing…', + 'engine.studio.data.tab.advanced': 'Advanced', + 'engine.studio.data.nameLabel': 'Object name', + 'engine.studio.data.idLabel': 'Identifier', + 'engine.studio.auto.nameLabel': 'Flow name', + 'engine.studio.auto.idLabel': 'Identifier', + 'engine.studio.access.nameLabel': 'Name', + 'engine.studio.access.idLabel': 'Identifier', + 'engine.studio.data.savedDraft': 'Object “{label}” saved as draft', + 'engine.studio.data.lastSaved': 'Saved {time}', + // Pillar tab labels + 'engine.fieldType.text': 'Text', + 'engine.fieldType.textarea': 'Text Area', + 'engine.fieldType.email': 'Email', + 'engine.fieldType.url': 'URL', + 'engine.fieldType.phone': 'Phone', + 'engine.fieldType.password': 'Password', + 'engine.fieldType.secret': 'Secret', + 'engine.fieldType.markdown': 'Markdown', + 'engine.fieldType.html': 'HTML', + 'engine.fieldType.richtext': 'Rich Text', + 'engine.fieldType.number': 'Number', + 'engine.fieldType.currency': 'Currency', + 'engine.fieldType.percent': 'Percent', + 'engine.fieldType.date': 'Date', + 'engine.fieldType.datetime': 'Date/Time', + 'engine.fieldType.time': 'Time', + 'engine.fieldType.boolean': 'Checkbox', + 'engine.fieldType.toggle': 'Toggle', + 'engine.fieldType.select': 'Picklist', + 'engine.fieldType.multiselect': 'Multi-Select', + 'engine.fieldType.radio': 'Radio', + 'engine.fieldType.checkboxes': 'Checkboxes', + 'engine.fieldType.lookup': 'Lookup', + 'engine.fieldType.master_detail': 'Master-Detail', + 'engine.fieldType.tree': 'Tree', + 'engine.fieldType.user': 'User', + 'engine.fieldType.image': 'Image', + 'engine.fieldType.file': 'File', + 'engine.fieldType.avatar': 'Avatar', + 'engine.fieldType.video': 'Video', + 'engine.fieldType.audio': 'Audio', + 'engine.fieldType.formula': 'Formula', + 'engine.fieldType.summary': 'Rollup', + 'engine.fieldType.autonumber': 'Auto Number', + 'engine.fieldType.composite': 'Composite', + 'engine.fieldType.repeater': 'Repeater', + 'engine.fieldType.record': 'Record Map', + 'engine.fieldType.location': 'Location', + 'engine.fieldType.address': 'Address', + 'engine.fieldType.code': 'Code', + 'engine.fieldType.json': 'JSON', + 'engine.fieldType.color': 'Color', + 'engine.fieldType.rating': 'Rating', + 'engine.fieldType.slider': 'Slider', + 'engine.fieldType.signature': 'Signature', + 'engine.fieldType.qrcode': 'QR Code', + 'engine.fieldType.progress': 'Progress', + 'engine.fieldType.tags': 'Tags', + 'engine.fieldType.vector': 'Vector', + // objectui#11793 — the one-line description under each type in the field + // inspector's Type picker. `@objectstack/spec` carries no per-type + // description (its `FieldType` enum has no `.describe()`), so they live here. + 'engine.fieldTypeDesc.text': 'A single line of text.', + 'engine.fieldTypeDesc.textarea': 'Several lines of plain text.', + 'engine.fieldTypeDesc.email': 'An email address.', + 'engine.fieldTypeDesc.url': 'A web address.', + 'engine.fieldTypeDesc.phone': 'A phone number.', + 'engine.fieldTypeDesc.password': 'Text that is masked when it is read back.', + 'engine.fieldTypeDesc.secret': 'A credential such as an API key, encrypted at rest and masked when read back.', + 'engine.fieldTypeDesc.markdown': 'Formatted text written in Markdown.', + 'engine.fieldTypeDesc.html': 'HTML markup.', + 'engine.fieldTypeDesc.richtext': 'Formatted text, edited visually.', + 'engine.fieldTypeDesc.number': 'A number, whole or decimal.', + 'engine.fieldTypeDesc.currency': 'An amount of money.', + 'engine.fieldTypeDesc.percent': 'A percentage.', + 'engine.fieldTypeDesc.date': 'A calendar date.', + 'engine.fieldTypeDesc.datetime': 'A date with a time of day.', + 'engine.fieldTypeDesc.time': 'A time of day.', + 'engine.fieldTypeDesc.boolean': 'Yes or no, shown as a checkbox.', + 'engine.fieldTypeDesc.toggle': 'Yes or no, shown as a switch.', + 'engine.fieldTypeDesc.select': 'One value from a list of options.', + 'engine.fieldTypeDesc.multiselect': 'Several values from a list of options.', + 'engine.fieldTypeDesc.radio': 'One value, shown as radio buttons.', + 'engine.fieldTypeDesc.checkboxes': 'Several values, shown as checkboxes.', + 'engine.fieldTypeDesc.lookup': 'A link to a record of another object.', + 'engine.fieldTypeDesc.master_detail': 'A link to the parent record that owns this one.', + 'engine.fieldTypeDesc.tree': 'A link to a parent record of the same object.', + 'engine.fieldTypeDesc.user': 'A link to a user, such as an owner or an assignee.', + 'engine.fieldTypeDesc.image': 'An uploaded image.', + 'engine.fieldTypeDesc.file': 'An uploaded file.', + 'engine.fieldTypeDesc.avatar': 'A profile picture.', + 'engine.fieldTypeDesc.video': 'An uploaded video.', + 'engine.fieldTypeDesc.audio': 'An uploaded audio clip.', + 'engine.fieldTypeDesc.formula': 'A value calculated by a formula.', + 'engine.fieldTypeDesc.summary': 'A value rolled up from related records.', + 'engine.fieldTypeDesc.autonumber': 'A number assigned to each new record.', + 'engine.fieldTypeDesc.composite': 'A group of sub-fields stored together.', + 'engine.fieldTypeDesc.repeater': 'A repeating list of sub-field groups.', + 'engine.fieldTypeDesc.record': 'Groups of sub-fields, each stored under its own name.', + 'engine.fieldTypeDesc.location': 'GPS coordinates.', + 'engine.fieldTypeDesc.address': 'A structured postal address.', + 'engine.fieldTypeDesc.code': 'Source code such as JSON, SQL or JavaScript.', + 'engine.fieldTypeDesc.json': 'Structured JSON data.', + 'engine.fieldTypeDesc.color': 'A color.', + 'engine.fieldTypeDesc.rating': 'A star rating.', + 'engine.fieldTypeDesc.slider': 'A number picked on a slider.', + 'engine.fieldTypeDesc.signature': 'A digital signature.', + 'engine.fieldTypeDesc.qrcode': 'A QR code or barcode.', + 'engine.fieldTypeDesc.progress': 'A value shown as a progress bar.', + 'engine.fieldTypeDesc.tags': 'A simple list of tags.', + 'engine.fieldTypeDesc.vector': 'Vector embeddings for AI semantic search.', + 'engine.fieldCategory.text': 'Text', + 'engine.fieldCategory.number': 'Number', + 'engine.fieldCategory.date': 'Date / time', + 'engine.fieldCategory.logic': 'Logic', + 'engine.fieldCategory.selection': 'Selection', + 'engine.fieldCategory.relation': 'Relation', + 'engine.fieldCategory.media': 'Media', + 'engine.fieldCategory.calculated': 'Calculated', + 'engine.fieldCategory.advanced': 'Advanced', + 'engine.studio.auto.deepLinkMissing': 'The link names flow “{name}”, which is not here.', + 'engine.studio.settings.basics': 'Basics', + 'engine.studio.settings.noInspector': 'No default object inspector registered.', + 'engine.studio.settings.semanticRoles': 'Semantic roles', + 'engine.studio.settings.semanticHint': 'Applied consistently across forms, lists and detail pages (ADR-0085)', + 'engine.studio.settings.nameField': 'Record name field (nameField) — shown in titles, links and references', + 'engine.studio.settings.autoDerive': '(auto-derived)', + 'engine.studio.settings.stageField': 'Lifecycle field (stageField) — the detail page progress bar renders its options', + 'engine.studio.settings.autoDetect': '(auto-detect status/stage-like field names)', + 'engine.studio.settings.stageNone': 'None — this object has no linear flow; hide the progress bar', + 'engine.studio.settings.highlightFields': 'Highlight fields (highlightFields) — the most important fields on this object, in display order; surfaces that show a record at a glance draw on them, each taking as many as it has room for', + 'engine.studio.settings.removeField': 'Remove {field}', + 'engine.studio.settings.addFieldOption': '+ Add field…', + 'engine.studio.settings.undeclared': '(not declared — surfaces pick heuristically)', + 'engine.studio.settings.sharing': 'Record sharing (OWD)', + 'engine.studio.settings.sharingHint': 'Baseline record visibility applied before positions and sharing rules (ADR-0056/0090)', + 'engine.studio.settings.sharingModel': 'Sharing model (sharingModel) — who can see and edit records another user owns', + 'engine.studio.settings.sharingUnset': '(not set — publishing is refused)', + 'engine.studio.settings.sharingPrivate': 'Private — owner only', + 'engine.studio.settings.sharingPublicRead': 'Public read — everyone reads, only the owner writes', + 'engine.studio.settings.sharingPublicReadWrite': 'Public read/write — everyone reads and writes', + 'engine.studio.settings.sharingControlledByParent': 'Controlled by parent — inherited from the master record', + 'engine.studio.settings.sharingDescUnset': + 'Not set — publishing this object is REFUSED (security-owd-unset). The runtime does fall back to Private (ADR-0090 D1), but the baseline has to be an authored decision, not an accident. Pick a model — Private is the same behaviour, chosen on purpose.', + 'engine.studio.settings.sharingDescPrivate': + 'Only the owner (plus users granted via positions or sharing rules) can access a record. Read / Edit permissions then apply to owned records only.', + 'engine.studio.settings.sharingDescPublicRead': + 'Every user in the tenant can read all records; only the owner can edit.', + 'engine.studio.settings.sharingDescPublicReadWrite': + 'Every user in the tenant can read and edit all records.', + 'engine.studio.settings.sharingDescControlledByParent': + 'Record visibility is inherited from the parent (master) record through its master-detail relationship.', + 'engine.studio.settings.capabilities': 'Capabilities (enable.*)', + 'engine.studio.settings.capabilitiesHint': 'Record-page features — every switch here is enforced by the runtime', + 'engine.studio.settings.capTrackHistory': 'History tab (trackHistory)', + 'engine.studio.settings.capTrackHistoryDesc': + 'Shows the read-only History tab (audit trail) on record pages. Pair with per-field history tracking for human-readable diffs; audit capture itself is always on for compliance.', + 'engine.studio.settings.capFiles': 'Attachments (files)', + 'engine.studio.settings.capFilesDesc': + 'Generic Attachments panel on record pages (upload/download/delete). Off = the server also rejects new attachments targeting this object.', + 'engine.studio.settings.capFeeds': 'Comments & feed (feeds)', + 'engine.studio.settings.capFeedsDesc': + 'Record discussion panel with @mentions and reactions. Off = the panel is hidden AND new comments are rejected server-side.', + 'engine.studio.settings.capActivities': 'Activity timeline (activities)', + 'engine.studio.settings.capActivitiesDesc': + 'Mirrors create/update/delete into the record timeline. Off = no timeline rows for this object (the compliance audit log is unaffected).', + 'engine.studio.settings.capSearchable': 'Global search (searchable)', + 'engine.studio.settings.capSearchableDesc': + "Include this object's records in cross-object global search. Off = records no longer appear in global search results. Which fields match is configured per-object via searchableFields, not here.", + 'engine.studio.settings.capClone': 'Cloning (clone)', + 'engine.studio.settings.capCloneDesc': + 'Allow duplicating records from the record page / API. Off = the clone endpoint returns 403 for this object.', + // Interfaces pillar + 'engine.studio.if.pickLeft': 'Select a menu item on the left', + // objectui#7254 — the tooltip that keeps the DEVELOPER identity reachable + // after the breadcrumb/caption stopped printing it beside a localized + // label. Prefixed rather than bare: `dashboard · customer_dashboard` on + // its own does not say what it is, and it is also what the rail item's + // own tooltip carries — three elements sharing one tooltip string is a + // page nothing (a person or a test) can address unambiguously. + 'engine.studio.if.internalId': 'Internal id', + 'engine.studio.if.navHeading': '{app} · Navigation', + 'engine.studio.if.editNavTitle': 'Edit navigation (drag to reorder / rename / add-remove)', + 'engine.studio.if.doneEditTitle': 'Done editing', + 'engine.studio.if.noApp': 'This package has no app yet.', + 'engine.studio.if.noNavItems': 'No nav items yet — (click “Edit” above to add)', + 'engine.studio.if.previewIsRuntime': 'Live preview', + 'engine.studio.if.modeDesign': 'Design', + 'engine.studio.if.modeRun': 'Run', + // objectui#12522 — a source page (`kind: 'html'` / `'react'`) in the Interfaces pillar. + 'engine.studio.if.modeSourcePage': + 'Run looks the same as Design here: a page written in {kind} has no designer chrome to hide.', + 'engine.studio.if.pageSaved': 'Saved', + 'engine.studio.if.pageSavedAt': 'Saved {time}', + 'engine.studio.if.pageUnsaved': 'Unsaved', + 'engine.studio.if.pageProps.label': 'Label', + 'engine.studio.if.pageProps.identifier': 'Identifier', + 'engine.studio.if.pageProps.kindFixed': 'Chosen when the page was created; it does not change here.', + 'engine.studio.if.pageProps.navEntries': 'Navigation entries', + 'engine.studio.if.pageProps.noNavEntries': 'No navigation entry opens this page.', + 'engine.studio.if.tabCanvas': 'Canvas', + 'engine.studio.if.noAppTitle': 'This package has no app yet', + 'engine.studio.if.noAppHint': 'Create an app to design its navigation and interfaces.', + // objectui#12508 — the canvas of an object that has only a draft: no runtime yet. + 'engine.studio.if.objectUnpublishedTitle': 'This object is not published yet', + 'engine.studio.if.objectUnpublishedHint': 'Publish the package to see its records here.', + /* objectui#6795 part C — the three designer registries (`preview-registry`, + * `inspector-registry`, `default-inspector-registry`) were plain `Map`s with + * no change notification, so a consumer that read one before registration + * landed NEVER recovered (measured on #6795: `late inspector rendered: + * false`). Part A has since landed in two steps (objectui#11939): the + * registries are observable and their readers re-render on registration + * (step 1), and the package entry registers the built-in designers from a + * chunk it loads with a dynamic `import()` (step 2). So a reader that renders + * before that chunk arrives shows these strings and then the designer; a + * session whose chunk never loads (or that registers no designers at all) + * keeps them. + * + * Every string in this family still states only what is TRUE at the moment + * it renders and promises nothing: no "loading…", no "try again", no + * spinner. Recovery is now real, but whether it comes depends on a chunk + * the string cannot see, so promising it would be a new false statement. + * + * The retired `engine.studio.if.readonlyPreview` ("{type} shows a read-only + * preview for now; design support is in progress.") was false twice over: this + * branch renders NO preview at all, and page design support exists — the + * sentence blamed the platform's roadmap for a chunk that did not load. It is + * split in two because the two causes are genuinely different and + * `listMetadataPreviewTypes()` tells them apart. */ + 'engine.studio.if.designersMissing': + 'No metadata designers are registered in this session, so {type} cannot be previewed or designed here.', + 'engine.studio.if.noDesigner': + 'No designer is registered for {type}, so it cannot be previewed or designed here.', + 'engine.studio.if.objectHintPre': 'Runtime list preview · edit fields / structure in the ', + 'engine.studio.if.objectHintPost': ' pillar', + /** + * objectui#12455 — the Interfaces rail's kind tags and its "+" kind picker. + * The four kinds the console also names as metadata types (object, page, + * dashboard, report) are named by `translateMetadataType`, so the rail and + * the header chip say one word; these are the rest. `engine.inspector.appNav.*` + * stays the Type field's own wording. + */ + 'engine.studio.if.kind.view': 'View', + 'engine.studio.if.kind.url': 'Link', + 'engine.studio.if.kind.action': 'Action', + 'engine.studio.if.kind.component': 'Component', + 'engine.studio.if.kind.doc': 'Doc', + 'engine.studio.if.kind.group': 'Group', + 'engine.studio.if.kind.separator': 'Separator', + 'engine.studio.if.kindDesc.object': 'A list view or one record', + 'engine.studio.if.kindDesc.page': 'A custom page built from blocks', + 'engine.studio.if.kindDesc.dashboard': 'Widgets over filters', + 'engine.studio.if.kindDesc.report': 'A saved report', + 'engine.studio.if.kindDesc.url': 'An external URL, same or new tab', + 'engine.studio.if.kindDesc.action': 'Runs a global action from the menu', + 'engine.studio.if.kindDesc.component': 'A registered component, like the approvals inbox', + 'engine.studio.if.kindDesc.doc': 'A doc page, a book, or both', + 'engine.studio.if.kindDesc.group': 'A titled section of the menu', + 'engine.studio.if.kindDesc.separator': 'A divider, no label, opens nothing', + 'engine.studio.if.addMenuTitle': 'Add to navigation', + 'engine.studio.if.emptyGroup': 'No items in this package', + 'engine.studio.if.openInData': 'Open in Data', + // objectui#12725 — the status line above the canvas, and the item's ⋯ menu. + 'engine.studio.if.status.design': 'Design', + 'engine.studio.if.status.run': 'Live preview', + 'engine.studio.if.status.unpublished': 'includes unpublished changes', + 'engine.studio.if.canvasHint.pick': 'Click an element → the right panel shows its properties', + 'engine.studio.if.canvasHint.selected': 'Selected: {label} · Esc to deselect', + 'engine.studio.if.more': 'More', + 'engine.studio.if.viewDefinition': 'View definition', + 'engine.studio.if.copyJson': 'Copy JSON', + 'engine.studio.if.copyFailed': 'Could not copy to the clipboard', + 'engine.studio.if.definitionTitle': '{label} · Definition', + // Data pillar + 'engine.studio.data.newFieldLabel': 'New field', + 'engine.studio.data.nameFieldLabel': 'Name', + 'engine.studio.data.idExists': 'Identifier “{name}” already exists', + 'engine.studio.data.fieldCount': '{count} fields', + 'engine.studio.data.pickObject': 'Select an object', + 'engine.studio.data.objects': 'Objects', + 'engine.studio.data.searchObjects': 'Search objects…', + 'engine.studio.data.noObjects': 'No objects yet — click “+” above to create one', + 'engine.studio.data.labelPlaceholder': 'Display name (e.g. Repair Ticket)', + 'engine.studio.data.idPlaceholder': 'Identifier (e.g. repair_ticket)', + // [`7a90afdf9`] The create dialog's third field. `新建对象` used to ask for + // exactly two things and produce an object the publish door refuses. + 'engine.studio.data.owdLabel': 'Record sharing (OWD) — who can see records another user owns', + 'engine.studio.data.owdHint': + 'Required to publish, and changeable later under Settings → Record sharing. Private matches what the runtime already does when no model is set.', + 'engine.studio.data.newObject': 'New object', + 'engine.studio.data.readOnlyPackage': 'This package is read-only — switch to or create a writable package to add objects', + 'engine.studio.data.firstObjectTitle': 'Start with your first object', + 'engine.studio.data.firstObjectHint': + 'Objects are your app’s data foundation (e.g. “Orders”, “Customers”). Create one below; then design its fields, forms, and automations, and publish once at the end.', + 'engine.studio.data.tab.records': 'Records', + 'engine.studio.data.tab.form': 'Form', + 'engine.studio.data.tab.rules': 'Validations', + 'engine.studio.data.tab.settings': 'Settings', + 'engine.studio.data.tab.hooks': 'Hooks', + 'engine.studio.data.tab.actions': 'Actions', + 'engine.studio.data.tab.api': 'API', + 'engine.studio.data.addField': 'Add field', + 'engine.studio.data.addFieldCreate': 'Create', + 'engine.studio.data.addFieldApiNameNotDerived': 'The label has no Latin letters to make an API name from. Type one: lowercase letters, digits and underscores.', + 'engine.studio.data.addFieldApiNameTaken': 'Another field is already named {name}. Type a different API name.', + 'engine.studio.data.editFieldProps': 'Edit field properties', + 'engine.studio.data.draftObjectTitle': 'Unpublished new object', + 'engine.studio.data.draftObjectHint': + 'The Records grid queries real data, but this object has no table until it’s published. Design fields and groups in “Form · Layout” first, then click “Publish” in the top bar — after publishing, this becomes its live data grid.', + 'engine.studio.data.goDesignFields': 'Go to “Form · Layout” to design fields', + 'engine.studio.data.form.layout': 'Layout', + 'engine.studio.data.form.preview': 'Preview', + // Two captions for the same tab: `layoutBadge` additionally CLAIMS pending + // local edits, so it may only render when there are some and they can be + // saved; `layoutBadgeClean` is the neutral caption for every other case + // (objectui#4036). + 'engine.studio.data.form.layoutBadge': 'Draft layout — your unsaved changes', + 'engine.studio.data.form.layoutBadgeClean': 'Draft layout', + // objectui#11781 — a read-only package has no draft layout: it says what it is. + 'engine.studio.data.form.layoutBadgeReadOnly': 'Read-only layout — this package cannot be edited', + 'engine.studio.data.form.previewBadge': 'Preview the form as the app renders it', + // objectui#12515 — Preview renders the draft definition; the chip says so, + // and the toggle (shown only beside a published version) picks the source. + 'engine.studio.data.form.draftChip': 'Draft preview', + 'engine.studio.data.form.draftChipTitle': + 'This preview renders the draft definition, unsaved changes included. Nothing is saved from here.', + 'engine.studio.data.form.sourceLabel': 'Preview source', + 'engine.studio.data.form.sourceDraft': 'Draft', + 'engine.studio.data.form.sourcePublished': 'Published', + 'engine.studio.data.fieldProps': 'Field properties', + 'engine.studio.data.groupProps': 'Group properties', + 'engine.studio.data.fieldInspectorMissing': + 'No field inspector is registered in this session, so this field’s properties cannot be edited here.', + // Automations pillar + 'engine.studio.auto.nodeStart': 'Start', + 'engine.studio.auto.nodeEnd': 'End', + 'engine.studio.auto.savedDraft': 'Automation “{label}” saved as draft', + 'engine.studio.auto.defaultOff': 'Off by default · review before enabling', + 'engine.studio.auto.heading': 'Automations', + 'engine.studio.auto.on': 'On', + 'engine.studio.auto.off': 'Off', + 'engine.studio.auto.offTitle': 'Disabled — will not run', + 'engine.studio.auto.onBound': 'Enabled · bound to its trigger', + 'engine.studio.auto.onUnbound': 'Enabled · no trigger (run manually)', + // objectui#11779 — an enabled flow whose declared trigger is not armed on this + // deployment; the title is for a backend that sends no `reason` (with one, + // the platform's sentence is shown instead). + 'engine.studio.auto.notRunning': 'Not running here', + 'engine.studio.auto.notRunningTitle': 'Enabled, but its trigger is not armed on this deployment', + 'engine.studio.auto.unpublishedTitle': 'Not deployed yet — publish to deploy it', + // objectui#12349 — the engine's registration verdict on a flow. A refused + // flow is titled with the engine's own message, as sent; an unregistered one + // reads "Not running here", titled by the row below. + 'engine.studio.auto.refused': 'Refused by the engine', + 'engine.studio.auto.unregisteredTitle': 'The automation engine on this deployment holds no registration for this flow', + 'engine.studio.auto.enabled': 'Enabled', + 'engine.studio.auto.disabled': 'Disabled', + 'engine.studio.auto.enableTitle': 'Enable this automation (publish to apply)', + 'engine.studio.auto.disableTitle': 'Disable this automation (publish to apply)', + 'engine.studio.auto.enabledToast': 'Enabled — publish to apply', + 'engine.studio.auto.disabledToast': 'Disabled — publish to apply', + 'engine.studio.auto.newTitle': 'New automation', + 'engine.studio.auto.none': 'No automations yet — click “+” above to create one', + 'engine.studio.auto.emptyMain': 'No automations in this package yet.', + 'engine.studio.auto.namePlaceholder': 'Name (e.g. Offer Notice)', + 'engine.studio.auto.idPlaceholder': 'Identifier (e.g. offer_notice)', + // objectui#11861 — the New automation dialog's starting points (`flowPresets.ts`). + 'engine.studio.auto.presets': 'When it runs', + 'engine.studio.auto.preset.created': 'When a record is created', + 'engine.studio.auto.preset.updated': 'When a record is updated', + 'engine.studio.auto.preset.deleted': 'When a record is deleted', + 'engine.studio.auto.preset.manual': 'When a button or another automation starts it', + 'engine.studio.auto.preset.needsObject': 'Choose the object it watches.', + 'engine.studio.auto.canvasHint': 'Visual orchestration · click a node to configure', + 'engine.studio.auto.config': 'Configuration', + 'engine.studio.auto.emptyLine1': 'Click a node on the canvas,', + 'engine.studio.auto.emptyLine2': 'and its configuration appears here.', + 'engine.studio.auto.designersMissing': + 'No metadata designers are registered in this session, so this flow cannot be designed here.', + // objectui#12454 — the rail's groups and legend: a flow's trigger kind, and + // the trailing group of flows that name no object. + 'engine.studio.auto.group.noObject': 'No object', + 'engine.studio.auto.kind.record': 'Record-triggered', + 'engine.studio.auto.kind.scheduled': 'Scheduled', + 'engine.studio.auto.kind.screen': 'Screen', + 'engine.studio.auto.kind.autolaunched': 'Autolaunched', + 'engine.studio.auto.legend': 'Legend', + // Access pillar + 'engine.studio.access.created': 'Permission set “{label}” created', + 'engine.studio.access.title': 'Permission matrix', + 'engine.studio.access.subtitle': 'Objects × CRUD · field-level R/W', + 'engine.studio.access.bannerTitle': + 'This matrix lists only the objects this package declares, and “Save” merges just that slice — grants contributed by other packages are preserved. Edits are saved as package drafts and go live when you Publish the package (top bar), exactly like Data and Interfaces.', + 'engine.studio.access.banner': 'This package’s objects · saved as draft', + // objectui#11781 — a read-only package saves no draft; objectui#12453 makes this the header link's title. + 'engine.studio.access.bannerTitleReadOnly': + 'This matrix lists only the objects this package declares. The package is read-only: its permission sets can be viewed here, but not changed.', + // ADR-0090 D5/D9 — pending suggested audience bindings (isDefault sets + // awaiting the admin's confirm; the server never auto-binds). + 'engine.studio.access.suggestPromptEveryone': 'This package suggests granting "{set}" to all signed-in users (Everyone).', + 'engine.studio.access.suggestPromptGuest': 'This package suggests granting "{set}" to unauthenticated visitors (Guest).', + 'engine.studio.access.suggestConfirm': 'Grant', + 'engine.studio.access.suggestConfirming': 'Granting…', + 'engine.studio.access.suggestDismiss': 'Dismiss', + 'engine.studio.access.suggestConfirmedToast': '"{set}" is now granted to {anchor}.', + 'engine.studio.access.suggestDismissedToast': 'Suggestion for "{set}" dismissed.', + // objectstack#22801 → A — an accept takes effect for the deployment; a + // dismiss on an accepted row revokes it; a `guest` row cannot be accepted. + 'engine.studio.access.suggestGuestUnsupported': 'Cannot be granted here: the Guest position takes no sets from accepted suggestions.', + 'engine.studio.access.suggestAcceptedState': '"{set}" was accepted for all signed-in users (Everyone).', + 'engine.studio.access.suggestNotInForceState': '"{set}" was accepted but is not in force yet: Everyone did not change on this request. The server log says why.', + 'engine.studio.access.suggestDismissedState': '"{set}" is not granted: the suggestion was dismissed or revoked.', + 'engine.studio.access.suggestRevoke': 'Revoke', + 'engine.studio.access.suggestRevoking': 'Revoking…', + 'engine.studio.access.suggestRevokedToast': 'Acceptance of "{set}" revoked.', + 'engine.studio.access.heading': 'Permission sets', + 'engine.studio.access.search': 'Search permissions…', + 'engine.studio.access.none': 'No permission sets yet — click “+” above to create one', + 'engine.studio.access.labelPlaceholder': 'Display name (e.g. Sales permissions)', + 'engine.studio.access.idPlaceholder': 'Identifier (e.g. sales_perms)', + 'engine.studio.access.new': 'New permission set', + 'engine.studio.access.emptyMain': 'No permission sets in this package yet.', + // objectui#12456 — the open set's item header and its five tabs. + 'engine.studio.access.tab.objects': 'Objects', + 'engine.studio.access.tab.capabilities': 'System capabilities', + 'engine.studio.access.tab.rls': 'Row-level security', + 'engine.studio.access.tab.tabs': 'Tab visibility', + 'engine.studio.access.tab.admin': 'Delegated admin', + 'engine.studio.access.grantedOne': '{count} object granted', + 'engine.studio.access.grantedOther': '{count} objects granted', + 'engine.studio.access.overridesOne': '{count} field override', + 'engine.studio.access.overridesOther': '{count} field overrides', + 'engine.studio.access.objectCountOne': '{count} object', + 'engine.studio.access.objectCountOther': '{count} objects', + 'engine.studio.access.objectCountFiltered': '{shown} of {count} objects', + 'engine.studio.access.more': 'More actions', + 'engine.studio.access.colOwd': 'OWD', + 'engine.studio.access.legend.create': 'Create', + 'engine.studio.access.legend.read': 'Read', + 'engine.studio.access.legend.edit': 'Edit', + 'engine.studio.access.legend.delete': 'Delete', + 'engine.studio.access.legend.transfer': 'Transfer', + 'engine.studio.access.legend.viewAll': 'View all', + 'engine.studio.access.legend.modifyAll': 'Modify all', + 'engine.studio.access.bulkHint': 'Bulk actions (R · CRUD · All · None) appear on the row under the pointer or with keyboard focus.', + // ── Access explain panel (ADR-0090 D6 — "why can this user access?") ──── + 'engine.studio.access.explain.open': 'Explain access', + 'engine.studio.access.explain.title': 'Why can this user access?', + 'engine.studio.access.explain.description': + 'Runs the same evaluation pipeline as enforcement and reports what each layer contributed — permission set → position → binding chain, OWD/sharing verdicts.', + 'engine.studio.access.explain.user': 'User', + 'engine.studio.access.explain.self': 'Me (calling principal)', + 'engine.studio.access.explain.pickUser': 'Select user…', + 'engine.studio.access.explain.clearUser': 'Reset to me', + 'engine.studio.access.explain.pickUserTitle': 'Select the user to explain', + 'engine.studio.access.explain.object': 'Object', + 'engine.studio.access.explain.objectPlaceholder': 'Object api-name', + 'engine.studio.access.explain.objectSelect': 'Select an object…', + 'engine.studio.access.explain.operation': 'Operation', + 'engine.studio.access.explain.run': 'Explain', + 'engine.studio.access.explain.running': 'Explaining…', + 'engine.studio.access.explain.allowedLine': '{operation} on “{object}” is ALLOWED for this principal.', + 'engine.studio.access.explain.deniedLine': '{operation} on “{object}” is DENIED for this principal.', + 'engine.studio.access.explain.principal': 'Principal', + 'engine.studio.access.explain.positions': 'Positions', + 'engine.studio.access.explain.permissionSets': 'Permission sets', + 'engine.studio.access.explain.layers': 'Evaluation pipeline', + 'engine.studio.access.explain.contributors': 'Contributed by', + 'engine.studio.access.explain.readFilter': 'Composed row filter (read)', + 'engine.studio.access.explain.empty': 'Pick an object and operation, then click “Explain”.', + 'engine.studio.access.explain.failed': 'Explain failed', + 'engine.studio.access.explain.forbidden': + 'You cannot explain that user — it requires the manage_users capability or a delegated admin scope covering them (ADR-0090 D6/D12).', + 'engine.studio.access.explain.layer.principal': 'Principal resolution', + 'engine.studio.access.explain.layer.required_permissions': 'Required capabilities', + 'engine.studio.access.explain.layer.object_crud': 'Object CRUD grant', + 'engine.studio.access.explain.layer.fls': 'Field-level security', + 'engine.studio.access.explain.layer.owd_baseline': 'Record baseline (OWD)', + 'engine.studio.access.explain.layer.depth': 'Scope depth', + 'engine.studio.access.explain.layer.sharing': 'Sharing', + 'engine.studio.access.explain.layer.vama_bypass': 'View/Modify All bypass', + 'engine.studio.access.explain.layer.rls': 'Row-level security', + 'engine.studio.access.explain.verdict.grants': 'grants', + 'engine.studio.access.explain.verdict.denies': 'denies', + 'engine.studio.access.explain.verdict.narrows': 'narrows', + 'engine.studio.access.explain.verdict.widens': 'widens', + 'engine.studio.access.explain.verdict.neutral': 'neutral', + 'engine.studio.access.explain.verdict.not_applicable': 'n/a', + 'engine.studio.access.explain.op.read': 'Read', + 'engine.studio.access.explain.op.create': 'Create', + 'engine.studio.access.explain.op.update': 'Update', + 'engine.studio.access.explain.op.delete': 'Delete', + 'engine.studio.access.explain.op.transfer': 'Transfer', + 'engine.studio.access.explain.op.restore': 'Restore', + 'engine.studio.access.explain.op.purge': 'Purge', + // [C2 / ADR-0095] record-grained explain + 'engine.studio.access.explain.record': 'Record (optional)', + 'engine.studio.access.explain.recordPlaceholder': 'Record id — explain one concrete row', + 'engine.studio.access.explain.pickRecord': 'Pick', + 'engine.studio.access.explain.clearRecord': 'Clear record', + 'engine.studio.access.explain.pickRecordTitle': 'Select the record to explain', + 'engine.studio.access.explain.recordVisibleLine': 'Record {recordId} is VISIBLE for this operation.', + 'engine.studio.access.explain.recordHiddenLine': 'Record {recordId} is NOT visible for this operation.', + 'engine.studio.access.explain.decidedBy': 'Decided by', + 'engine.studio.access.explain.postureHint': 'The posture rung the principal was evaluated at (ADR-0095 D2).', + 'engine.studio.access.explain.posture.PLATFORM_ADMIN': 'Platform admin', + 'engine.studio.access.explain.posture.TENANT_ADMIN': 'Tenant admin', + 'engine.studio.access.explain.posture.MEMBER': 'Member', + 'engine.studio.access.explain.posture.EXTERNAL': 'External', + 'engine.studio.access.explain.layer.tenant_isolation': 'Tenant isolation (Layer 0)', + 'engine.studio.access.explain.kernelTier.layer_0_tenant': 'Tenant wall', + 'engine.studio.access.explain.kernelTier.layer_1_business': 'Business RLS', + 'engine.studio.access.explain.kernelTierHint': 'Layer 0 = the always-first org wall; Layer 1 = business row-level security.', + 'engine.studio.access.explain.recordOutcome': 'This record', + 'engine.studio.access.explain.outcome.admitted': 'admitted', + 'engine.studio.access.explain.outcome.excluded': 'excluded', + 'engine.studio.access.explain.outcome.not_evaluated': 'not evaluated', + 'engine.studio.access.explain.matches': 'record matches', + 'engine.studio.access.explain.noMatch': 'record does not match', + 'engine.studio.access.explain.effectiveRowFilter': 'Effective row filter', + 'engine.studio.access.explain.ruleKind.tenant_filter': 'Tenant filter', + 'engine.studio.access.explain.ruleKind.owd_baseline': 'OWD baseline', + 'engine.studio.access.explain.ruleKind.ownership': 'Ownership', + 'engine.studio.access.explain.ruleKind.record_share': 'Record share', + 'engine.studio.access.explain.ruleKind.sharing_rule': 'Sharing rule', + 'engine.studio.access.explain.ruleKind.team': 'Team', + 'engine.studio.access.explain.ruleKind.territory': 'Territory', + 'engine.studio.access.explain.ruleKind.rls_policy': 'RLS policy', }; 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 bcccf115b5d..4d8a972f014 100644 --- a/ui/packages/i18n/src/namespaces/studio/zh.ts +++ b/ui/packages/i18n/src/namespaces/studio/zh.ts @@ -1602,6 +1602,593 @@ const STUDIO_ZH: Record = { // zh has one plural form, so the `…One` / `…Other` pair carries the same row. 'engine.packages.detail.adoptedOne': '已收编 {count} 个散落项进此基座。', 'engine.packages.detail.adoptedOther': '已收编 {count} 个散落项进此基座。', + // engine.studio.{access,if,data,auto,settings}, the data source pages + // (engine.datasource, engine.externalDatasource, engine.datasourcePreview) and + // the field types (engine.fieldType, engine.fieldTypeDesc, engine.fieldCategory) + // (#25922 slice S4). + 'engine.datasource.deleteTitle': '删除数据源“{item}”?', + 'engine.datasource.deleteBody': '连接 {name} 将被移除。此操作不可撤销。', + 'engine.datasource.delete': '删除', + 'engine.datasource.title': '数据源', + 'engine.datasource.subtitle': '连接外部数据库,并将其中的表同步为对象。', + 'engine.datasource.refresh': '刷新', + 'engine.datasource.new': '新建数据源', + 'engine.datasource.loading': '加载中…', + 'engine.datasource.empty': '还没有数据源。点击{action}连接一个。', + 'engine.datasource.test': '测试', + 'engine.datasource.sync': '同步对象', + 'engine.datasource.edit': '编辑', + 'engine.datasource.statusOk': '已连接', + 'engine.datasource.statusError': '连接失败', + 'engine.datasource.statusBlocked': '被策略阻止', + 'engine.datasource.statusUnvalidated': '未连接', + 'engine.datasource.editTitle': '编辑“{name}”', + 'engine.datasource.editDescription': '更新连接。凭据留空则保留当前凭据。', + 'engine.datasource.newDescription': '连接外部数据库。凭据会加密保存,绝不写入元数据。', + 'engine.datasource.name': '名称', + 'engine.datasource.nameInvalid': '仅限小写字母、数字和下划线,且不能以数字开头。', + 'engine.datasource.label': '显示名称', + 'engine.datasource.labelPlaceholder': '便于识别的名称', + 'engine.datasource.driver': '驱动', + 'engine.datasource.driverPlaceholder': '选择驱动', + 'engine.datasource.schemaMode': '结构模式', + 'engine.datasource.schemaModeManaged': '托管(表结构由 ObjectStack 管理)', + 'engine.datasource.schemaModeExternal': '外部(按原样读取现有表结构)', + 'engine.datasource.schemaModeValidateOnly': '仅校验', + 'engine.datasource.testConnection': '测试连接', + 'engine.datasource.connectionFailed': '连接失败', + 'engine.datasource.connectionOk': '连接正常', + 'engine.datasource.connectionOkLatency': '连接正常({latency} 毫秒)', + 'engine.datasource.saveChanges': '保存更改', + 'engine.datasource.create': '创建数据源', + 'engine.datasource.syncTitle': '从“{name}”同步对象', + 'engine.datasource.syncDescription': '选择要作为对象导入的远程表。每张表都会成为一个对象定义。', + 'engine.datasource.introspecting': '正在读取远程表…', + 'engine.datasource.noRemoteTables': '此数据源上没有找到远程表。', + 'engine.datasource.columnCount': '{count} 列', + 'engine.datasource.createObjectsNone': '创建对象', + 'engine.datasource.createObjectsOne': '创建 1 个对象', + 'engine.datasource.createObjects': '创建 {count} 个对象', + 'engine.datasource.failed': '失败', + 'engine.datasource.rowTestOk': '{name}:连接正常', + 'engine.datasource.rowTestOkLatency': '{name}:连接正常({latency} 毫秒)', + 'engine.datasource.loadFailed': '加载数据源失败:{error}', + 'engine.datasource.loadDriversFailed': '加载驱动失败:{error}', + 'engine.datasource.openFailed': '打开 {name} 失败:{error}', + 'engine.datasource.deleted': '已删除 {name}。', + 'engine.datasource.deleteFailed': '删除 {name} 失败:{error}', + 'engine.datasource.created': '已创建 {name}。', + 'engine.datasource.updated': '已更新 {name}。', + 'engine.datasource.saveFailed': '保存失败:{error}', + 'engine.datasource.introspectFailed': '读取 {name} 的远程表失败:{error}', + 'engine.datasource.syncedOne': '已从 {name} 同步 1 个对象。', + 'engine.datasource.synced': '已从 {name} 同步 {count} 个对象。', + 'engine.datasource.syncFailed': '失败:{failures}', + 'engine.datasourcePreview.empty': '设置名称并至少指定一个驱动,即可查看数据源预览。', + 'engine.datasourcePreview.unknownDriver': '未知', + 'engine.datasourcePreview.active': '已启用', + 'engine.datasourcePreview.disabled': '已停用', + 'engine.datasourcePreview.connection': '连接', + 'engine.datasourcePreview.noConfig': '未设置任何配置项。', + 'engine.datasourcePreview.redacted': '已隐去', + 'engine.datasourcePreview.pool': '连接池', + 'engine.datasourcePreview.notConfigured': '未配置', + 'engine.datasourcePreview.keyCount': '{{count} 个键}', + 'engine.externalDatasource.unavailable': '此服务器未启用数据联邦。', + 'engine.externalDatasource.saveFirst': '请先保存数据源,才能浏览其远程表并校验联邦对象。', + 'engine.externalDatasource.title': '外部数据源', + 'engine.externalDatasource.writesAllowed': '允许写入', + 'engine.externalDatasource.readOnly': '只读', + 'engine.externalDatasource.snapshot': '快照 {time}', + 'engine.externalDatasource.refresh': '刷新目录', + 'engine.externalDatasource.tables': '表', + 'engine.externalDatasource.validation': '校验', + // objectui#10862, slice 4 — SchemaBrowser, ValidationPanel, ImportObjectDialog. + 'engine.externalDatasource.browser.filter': '筛选表…', + 'engine.externalDatasource.browser.refresh': '刷新', + 'engine.externalDatasource.browser.introspecting': '正在读取远程表结构…', + 'engine.externalDatasource.browser.noRemoteTables': '未找到远程表(请检查数据源的 allowedSchemas)。', + 'engine.externalDatasource.browser.noMatch': '没有与筛选条件匹配的表。', + 'engine.externalDatasource.browser.colTable': '表名', + 'engine.externalDatasource.browser.colColumns': '列', + 'engine.externalDatasource.browser.colRows': '行数(估算)', + 'engine.externalDatasource.browser.import': '导入', + 'engine.externalDatasource.browser.unavailable': + '此服务器未启用数据联邦。必须注册 {service} 服务,才能浏览表、生成草稿并进行校验。', + 'engine.externalDatasource.check.unavailable': '此服务器未启用数据联邦,因此无法进行校验。', + 'engine.externalDatasource.check.intro': '检查 {datasource} 上的每个联邦对象是否仍与其实时远程表一致。', + 'engine.externalDatasource.check.run': '运行校验', + 'engine.externalDatasource.check.allMatchOne': '全部 {count} 个对象均与远程表结构一致。', + 'engine.externalDatasource.check.allMatchOther': '全部 {count} 个对象均与远程表结构一致。', + 'engine.externalDatasource.check.divergeOne': '{count} 个对象中有 {diverged} 个与远程表结构不一致。', + 'engine.externalDatasource.check.divergeOther': '{count} 个对象中有 {diverged} 个与远程表结构不一致。', + 'engine.externalDatasource.check.noObjects': '尚无联邦对象绑定到此数据源。请在“表”标签页中导入一张表以开始。', + 'engine.externalDatasource.check.diffsOne': '{count} 处差异', + 'engine.externalDatasource.check.diffsOther': '{count} 处差异', + 'engine.externalDatasource.check.expectedActual': '预期 {expected},实际 {actual}', + 'engine.externalDatasource.diff.missingTable': '缺少表', + 'engine.externalDatasource.diff.missingColumn': '缺少列', + 'engine.externalDatasource.diff.typeMismatch': '类型不匹配', + 'engine.externalDatasource.diff.nullabilityMismatch': '可空性不匹配', + 'engine.externalDatasource.diff.unmappedColumn': '未映射的列', + 'engine.externalDatasource.diff.pkMismatch': '主键不匹配', + 'engine.externalDatasource.diff.indexMismatch': '索引不匹配', + 'engine.externalDatasource.diff.unmappedIndex': '未映射的索引', + 'engine.externalDatasource.diff.defaultMismatch': '列默认值不匹配', + 'engine.externalDatasource.diff.unreachable': '未检查 —— 远程不可达', + 'engine.externalDatasource.import.title': '导入为对象', + 'engine.externalDatasource.import.intro': + '将远程表 {table} 映射为绑定到 {datasource} 的 ObjectStack 对象。远程表结构不会被修改。', + 'engine.externalDatasource.import.generating': '正在生成草稿…', + 'engine.externalDatasource.import.done': '已导入对象 {name}。', + 'engine.externalDatasource.import.doneNext': '请复查其绑定并运行校验,确认它与远程表一致。', + 'engine.externalDatasource.import.objectName': '对象名称', + 'engine.externalDatasource.import.reviewOne': '{count} 个列需要复查', + 'engine.externalDatasource.import.reviewOther': '{count} 个列需要复查', + 'engine.externalDatasource.import.source': '生成的源码', + 'engine.externalDatasource.import.importing': '正在导入…', + 'engine.studio.data.tab.advanced': '高级', + 'engine.studio.data.nameLabel': '对象名称', + 'engine.studio.data.idLabel': '标识', + 'engine.studio.auto.nameLabel': '流程名称', + 'engine.studio.auto.idLabel': '标识', + 'engine.studio.access.nameLabel': '名称', + 'engine.studio.access.idLabel': '标识', + 'engine.studio.data.savedDraft': '对象「{label}」已存为草稿', + 'engine.studio.data.lastSaved': '已于 {time} 保存', + // Pillar tab labels + 'engine.fieldType.text': '单行文本', + 'engine.fieldType.textarea': '多行文本', + 'engine.fieldType.email': '邮箱', + 'engine.fieldType.url': '网址', + 'engine.fieldType.phone': '电话', + 'engine.fieldType.password': '密码', + 'engine.fieldType.secret': '密钥', + 'engine.fieldType.markdown': 'Markdown', + 'engine.fieldType.html': 'HTML', + 'engine.fieldType.richtext': '富文本', + 'engine.fieldType.number': '数字', + 'engine.fieldType.currency': '货币', + 'engine.fieldType.percent': '百分比', + 'engine.fieldType.date': '日期', + 'engine.fieldType.datetime': '日期时间', + 'engine.fieldType.time': '时间', + 'engine.fieldType.boolean': '复选框', + 'engine.fieldType.toggle': '开关', + 'engine.fieldType.select': '下拉选择', + 'engine.fieldType.multiselect': '多选', + 'engine.fieldType.radio': '单选', + 'engine.fieldType.checkboxes': '复选组', + 'engine.fieldType.lookup': '查找关系', + 'engine.fieldType.master_detail': '主从关系', + 'engine.fieldType.tree': '树形关系', + 'engine.fieldType.user': '用户', + 'engine.fieldType.image': '图片', + 'engine.fieldType.file': '文件', + 'engine.fieldType.avatar': '头像', + 'engine.fieldType.video': '视频', + 'engine.fieldType.audio': '音频', + 'engine.fieldType.formula': '公式', + 'engine.fieldType.summary': '汇总', + 'engine.fieldType.autonumber': '自动编号', + 'engine.fieldType.composite': '复合字段', + 'engine.fieldType.repeater': '重复字段', + 'engine.fieldType.record': '命名分组', + 'engine.fieldType.location': '地理坐标', + 'engine.fieldType.address': '地址', + 'engine.fieldType.code': '代码', + 'engine.fieldType.json': 'JSON', + 'engine.fieldType.color': '颜色', + 'engine.fieldType.rating': '评分', + 'engine.fieldType.slider': '滑块', + 'engine.fieldType.signature': '签名', + 'engine.fieldType.qrcode': '二维码', + 'engine.fieldType.progress': '进度条', + 'engine.fieldType.tags': '标签', + 'engine.fieldType.vector': '向量', + // objectui#11793. + 'engine.fieldTypeDesc.text': '单行文本。', + 'engine.fieldTypeDesc.textarea': '多行纯文本。', + 'engine.fieldTypeDesc.email': '电子邮箱地址。', + 'engine.fieldTypeDesc.url': '网页地址。', + 'engine.fieldTypeDesc.phone': '电话号码。', + 'engine.fieldTypeDesc.password': '读取时会被遮蔽的文本。', + 'engine.fieldTypeDesc.secret': '加密存储的凭据(如 API 密钥),读取时会被遮蔽。', + 'engine.fieldTypeDesc.markdown': '用 Markdown 书写的格式化文本。', + 'engine.fieldTypeDesc.html': 'HTML 标记。', + 'engine.fieldTypeDesc.richtext': '可视化编辑的格式化文本。', + 'engine.fieldTypeDesc.number': '数字,整数或小数。', + 'engine.fieldTypeDesc.currency': '金额。', + 'engine.fieldTypeDesc.percent': '百分比。', + 'engine.fieldTypeDesc.date': '日历日期。', + 'engine.fieldTypeDesc.datetime': '带时间的日期。', + 'engine.fieldTypeDesc.time': '一天中的时间。', + 'engine.fieldTypeDesc.boolean': '是或否,显示为复选框。', + 'engine.fieldTypeDesc.toggle': '是或否,显示为开关。', + 'engine.fieldTypeDesc.select': '从选项列表中选一个值。', + 'engine.fieldTypeDesc.multiselect': '从选项列表中选多个值。', + 'engine.fieldTypeDesc.radio': '选一个值,显示为单选按钮。', + 'engine.fieldTypeDesc.checkboxes': '选多个值,显示为复选框。', + 'engine.fieldTypeDesc.lookup': '关联到另一个对象的一条记录。', + 'engine.fieldTypeDesc.master_detail': '关联到拥有本记录的父记录。', + 'engine.fieldTypeDesc.tree': '关联到同一对象中的父记录。', + 'engine.fieldTypeDesc.user': '关联到一位用户,如负责人或处理人。', + 'engine.fieldTypeDesc.image': '上传的图片。', + 'engine.fieldTypeDesc.file': '上传的文件。', + 'engine.fieldTypeDesc.avatar': '头像图片。', + 'engine.fieldTypeDesc.video': '上传的视频。', + 'engine.fieldTypeDesc.audio': '上传的音频。', + 'engine.fieldTypeDesc.formula': '由公式计算得出的值。', + 'engine.fieldTypeDesc.summary': '汇总相关记录得出的值。', + 'engine.fieldTypeDesc.autonumber': '为每条新记录分配的编号。', + 'engine.fieldTypeDesc.composite': '一组存放在一起的子字段。', + 'engine.fieldTypeDesc.repeater': '可重复的一组子字段列表。', + 'engine.fieldTypeDesc.record': '多组子字段,每组以自己的名称存放。', + 'engine.fieldTypeDesc.location': 'GPS 坐标。', + 'engine.fieldTypeDesc.address': '结构化的邮寄地址。', + 'engine.fieldTypeDesc.code': 'JSON、SQL 或 JavaScript 等源代码。', + 'engine.fieldTypeDesc.json': '结构化的 JSON 数据。', + 'engine.fieldTypeDesc.color': '颜色。', + 'engine.fieldTypeDesc.rating': '星级评分。', + 'engine.fieldTypeDesc.slider': '用滑块选取的数字。', + 'engine.fieldTypeDesc.signature': '电子签名。', + 'engine.fieldTypeDesc.qrcode': '二维码或条形码。', + 'engine.fieldTypeDesc.progress': '以进度条显示的值。', + 'engine.fieldTypeDesc.tags': '简单的标签列表。', + 'engine.fieldTypeDesc.vector': '用于 AI 语义搜索的向量嵌入。', + 'engine.fieldCategory.text': '文本', + 'engine.fieldCategory.number': '数值', + 'engine.fieldCategory.date': '日期/时间', + 'engine.fieldCategory.logic': '逻辑', + 'engine.fieldCategory.selection': '选择', + 'engine.fieldCategory.relation': '关系', + 'engine.fieldCategory.media': '媒体', + 'engine.fieldCategory.calculated': '计算', + 'engine.fieldCategory.advanced': '高级', + 'engine.studio.auto.deepLinkMissing': '链接指向的流程「{name}」不在这里。', + 'engine.studio.settings.basics': '基础信息', + 'engine.studio.settings.noInspector': '未注册对象默认检查器。', + 'engine.studio.settings.semanticRoles': '语义角色', + 'engine.studio.settings.semanticHint': '跨表单 / 列表 / 详情统一生效(ADR-0085)', + 'engine.studio.settings.nameField': '记录名称字段(nameField)—— 标题、链接、引用处显示的字段', + 'engine.studio.settings.autoDerive': '(自动推导)', + 'engine.studio.settings.stageField': '生命周期字段(stageField)—— 详情页顶部进度条按它的选项渲染', + 'engine.studio.settings.autoDetect': '(自动探测 status/stage 等字段名)', + 'engine.studio.settings.stageNone': '无 —— 这个对象没有线性流程,不显示进度条', + 'engine.studio.settings.highlightFields': '重点字段(highlightFields)—— 本对象最重要的字段,顺序即展示顺序;需要概览展示一条记录的界面都从中取用,各自能放下多少取多少', + 'engine.studio.settings.removeField': '移除 {field}', + 'engine.studio.settings.addFieldOption': '+ 添加字段…', + 'engine.studio.settings.undeclared': '(未声明 —— 各处按启发式自动挑选)', + 'engine.studio.settings.sharing': '记录共享模型(OWD)', + 'engine.studio.settings.sharingHint': '在岗位与共享规则之前生效的记录可见性基线(ADR-0056/0090)', + 'engine.studio.settings.sharingModel': '共享模型(sharingModel)—— 谁能查看和编辑他人拥有的记录', + 'engine.studio.settings.sharingUnset': '(未设置 —— 发布会被拒绝)', + 'engine.studio.settings.sharingPrivate': 'Private 私有 —— 仅记录所有者', + 'engine.studio.settings.sharingPublicRead': 'Public read 公共只读 —— 所有人可读,仅所有者可写', + 'engine.studio.settings.sharingPublicReadWrite': 'Public read/write 公共读写 —— 所有人可读可写', + 'engine.studio.settings.sharingControlledByParent': 'Controlled by parent 受父级控制 —— 继承自主记录', + 'engine.studio.settings.sharingDescUnset': + '未设置 —— 该对象无法发布(security-owd-unset)。运行时确实会回落为 Private(ADR-0090 D1),但这条基线必须是显式作出的决定,而不是疏漏。请选择一个模型 —— 选 Private 的行为完全相同,区别在于它是有意选定的。', + 'engine.studio.settings.sharingDescPrivate': + '只有所有者(以及经岗位或共享规则授予的用户)能访问记录。此时读取 / 编辑权限仅作用于自己拥有的记录。', + 'engine.studio.settings.sharingDescPublicRead': + '租户内每个用户都能读取所有记录;仅所有者可编辑。', + 'engine.studio.settings.sharingDescPublicReadWrite': + '租户内每个用户都能读取并编辑所有记录。', + 'engine.studio.settings.sharingDescControlledByParent': + '记录可见性通过主从(master-detail)关系继承自父级(主)记录。', + 'engine.studio.settings.capabilities': '能力开关(enable.*)', + 'engine.studio.settings.capabilitiesHint': '记录页能力 —— 这里的每个开关都由运行时真实强制', + 'engine.studio.settings.capTrackHistory': '历史标签页(trackHistory)', + 'engine.studio.settings.capTrackHistoryDesc': + '在记录页显示只读的历史标签页(审计轨迹)。配合字段级历史跟踪可得到可读的字段级差异;审计采集本身始终开启(合规要求)。', + 'engine.studio.settings.capFiles': '附件(files)', + 'engine.studio.settings.capFilesDesc': + '记录页通用附件面板(上传/下载/删除)。关闭后服务端同时拒绝指向该对象的新附件。', + 'engine.studio.settings.capFeeds': '评论与动态(feeds)', + 'engine.studio.settings.capFeedsDesc': + '记录讨论面板(支持 @提及 与表情回应)。关闭后面板隐藏,且服务端拒绝新评论。', + 'engine.studio.settings.capActivities': '活动时间线(activities)', + 'engine.studio.settings.capActivitiesDesc': + '将创建/更新/删除镜像到记录时间线。关闭后该对象不再产生时间线记录(合规审计日志不受影响)。', + 'engine.studio.settings.capSearchable': '全局搜索(searchable)', + 'engine.studio.settings.capSearchableDesc': + '将该对象的记录纳入跨对象全局搜索。关闭后记录不再出现在全局搜索结果中。匹配哪些字段由对象的 searchableFields 配置,与此开关无关。', + 'engine.studio.settings.capClone': '克隆(clone)', + 'engine.studio.settings.capCloneDesc': + '允许从记录页/API 复制记录。关闭后克隆接口对该对象返回 403。', + // Interfaces pillar + 'engine.studio.if.pickLeft': '从左侧选择一个菜单项', + 'engine.studio.if.internalId': '内部标识', + 'engine.studio.if.navHeading': '{app} · 导航', + 'engine.studio.if.editNavTitle': '编辑导航(拖拽排序 / 重命名 / 增删)', + 'engine.studio.if.doneEditTitle': '完成编辑', + 'engine.studio.if.noApp': '这个软件包还没有应用。', + 'engine.studio.if.noNavItems': '还没有导航项 —(点上方「编辑」添加)', + 'engine.studio.if.previewIsRuntime': '实时预览', + 'engine.studio.if.modeDesign': '设计', + 'engine.studio.if.modeRun': '运行', + 'engine.studio.if.modeSourcePage': '此处运行与设计显示相同:以 {kind} 编写的页面没有可隐藏的设计器元素。', + 'engine.studio.if.pageSaved': '已保存', + 'engine.studio.if.pageSavedAt': '已于 {time} 保存', + 'engine.studio.if.pageUnsaved': '未保存', + 'engine.studio.if.pageProps.label': '标签', + 'engine.studio.if.pageProps.identifier': '标识', + 'engine.studio.if.pageProps.kindFixed': '创建页面时选定,此处不可更改。', + 'engine.studio.if.pageProps.navEntries': '导航入口', + 'engine.studio.if.pageProps.noNavEntries': '没有导航入口打开此页面。', + 'engine.studio.if.tabCanvas': '画布', + 'engine.studio.if.noAppTitle': '这个软件包还没有应用', + 'engine.studio.if.noAppHint': '创建一个应用来设计它的导航与界面。', + 'engine.studio.if.objectUnpublishedTitle': '这个对象还没有发布', + 'engine.studio.if.objectUnpublishedHint': '发布软件包后,这里会显示它的记录。', + 'engine.studio.if.designersMissing': '本次会话没有注册任何元数据设计器,因此无法在这里预览或设计 {type}。', + 'engine.studio.if.noDesigner': '没有为 {type} 注册设计器,因此无法在这里预览或设计。', + 'engine.studio.if.objectHintPre': '运行态列表预览 · 改字段 / 结构请到「', + 'engine.studio.if.objectHintPost': '」', + 'engine.studio.if.kind.view': '视图', + 'engine.studio.if.kind.url': '链接', + 'engine.studio.if.kind.action': '动作', + 'engine.studio.if.kind.component': '组件', + 'engine.studio.if.kind.doc': '文档', + 'engine.studio.if.kind.group': '分组', + 'engine.studio.if.kind.separator': '分隔线', + 'engine.studio.if.kindDesc.object': '一个列表视图或一条记录', + 'engine.studio.if.kindDesc.page': '由区块搭建的自定义页面', + 'engine.studio.if.kindDesc.dashboard': '带筛选的部件看板', + 'engine.studio.if.kindDesc.report': '一份已保存的报表', + 'engine.studio.if.kindDesc.url': '外部链接,在当前或新标签页打开', + 'engine.studio.if.kindDesc.action': '从菜单运行一个全局动作', + 'engine.studio.if.kindDesc.component': '一个已注册的组件,例如审批收件箱', + 'engine.studio.if.kindDesc.doc': '一个文档页、一本文档册,或两者', + 'engine.studio.if.kindDesc.group': '菜单中带标题的一个分区', + 'engine.studio.if.kindDesc.separator': '一条分隔线,无标签,不打开任何内容', + 'engine.studio.if.addMenuTitle': '添加到导航', + 'engine.studio.if.emptyGroup': '这个软件包里没有条目', + 'engine.studio.if.openInData': '在「数据」中打开', + // objectui#12725 — 画布上方的状态行,以及条目的 ⋯ 菜单。 + 'engine.studio.if.status.design': '设计态', + 'engine.studio.if.status.run': '运行态预览', + 'engine.studio.if.status.unpublished': '含未发布改动', + 'engine.studio.if.canvasHint.pick': '点元素 → 右栏切到属性', + 'engine.studio.if.canvasHint.selected': '已选中:{label} · Esc 取消选中', + 'engine.studio.if.more': '更多', + 'engine.studio.if.viewDefinition': '查看定义', + 'engine.studio.if.copyJson': '复制 JSON', + 'engine.studio.if.copyFailed': '无法复制到剪贴板', + 'engine.studio.if.definitionTitle': '{label} · 定义', + // Data pillar + 'engine.studio.data.newFieldLabel': '新字段', + 'engine.studio.data.nameFieldLabel': '名称', + 'engine.studio.data.idExists': '标识符 "{name}" 已存在', + 'engine.studio.data.fieldCount': '{count} 字段', + 'engine.studio.data.pickObject': '选择一个对象', + 'engine.studio.data.objects': '对象', + 'engine.studio.data.searchObjects': '搜索对象…', + 'engine.studio.data.noObjects': '还没有对象 — 点上方的「+」新建', + 'engine.studio.data.labelPlaceholder': '显示名(如:报修工单)', + 'engine.studio.data.idPlaceholder': '标识符(如:repair_ticket)', + // [`7a90afdf9`] 新建对象对话框的第三项。此前只问两项,产出的对象会被发布门拒绝。 + 'engine.studio.data.owdLabel': '记录共享模型(OWD)—— 谁能看到他人拥有的记录', + 'engine.studio.data.owdHint': + '发布必填,之后可在“设置 → 记录共享模型”中修改。Private 与未设置时运行时的实际行为一致。', + 'engine.studio.data.newObject': '新建对象', + 'engine.studio.data.readOnlyPackage': '此包为只读——请切换到或新建一个可写的包后再添加对象', + 'engine.studio.data.firstObjectTitle': '从第一个对象开始', + 'engine.studio.data.firstObjectHint': + '对象是应用的数据基座(如「订单」「客户」)。点击下方按钮创建一个;之后为它设计字段、表单与自动化,最后一次发布。', + 'engine.studio.data.tab.records': '记录', + 'engine.studio.data.tab.form': '表单', + 'engine.studio.data.tab.rules': '验证', + 'engine.studio.data.tab.settings': '设置', + 'engine.studio.data.tab.hooks': '钩子', + 'engine.studio.data.tab.actions': '操作', + 'engine.studio.data.tab.api': 'API', + 'engine.studio.data.addField': '添加字段', + 'engine.studio.data.addFieldCreate': '创建', + 'engine.studio.data.addFieldApiNameNotDerived': '标签里没有可用来生成 API 名称的英文字母,请手动填写(小写字母、数字和下划线)。', + 'engine.studio.data.addFieldApiNameTaken': '已有字段名为 {name},请填写其他 API 名称。', + 'engine.studio.data.editFieldProps': '编辑字段属性', + 'engine.studio.data.draftObjectTitle': '未发布的新对象', + 'engine.studio.data.draftObjectHint': + '「记录」网格查询真实数据,而这个对象发布前还没有数据表。请先在「表单 · 布局」里设计字段与分组,然后点顶栏「发布」— 发布后这里就是它的实时数据网格。', + 'engine.studio.data.goDesignFields': '去「表单 · 布局」设计字段', + 'engine.studio.data.form.layout': '布局', + 'engine.studio.data.form.preview': '预览', + 'engine.studio.data.form.layoutBadge': '草稿布局 · 含未保存改动', + 'engine.studio.data.form.layoutBadgeClean': '草稿布局', + 'engine.studio.data.form.layoutBadgeReadOnly': '只读布局 · 本软件包不可编辑', + 'engine.studio.data.form.previewBadge': '按应用中的样子预览表单', + 'engine.studio.data.form.draftChip': '草稿预览', + 'engine.studio.data.form.draftChipTitle': '此预览渲染草稿定义,含未保存的改动。这里不会保存任何内容。', + 'engine.studio.data.form.sourceLabel': '预览来源', + 'engine.studio.data.form.sourceDraft': '草稿', + 'engine.studio.data.form.sourcePublished': '已发布', + 'engine.studio.data.fieldProps': '字段属性', + 'engine.studio.data.groupProps': '分组属性', + 'engine.studio.data.fieldInspectorMissing': '本次会话没有注册字段检查器,因此无法在这里编辑该字段的属性。', + // Automations pillar + 'engine.studio.auto.nodeStart': '开始', + 'engine.studio.auto.nodeEnd': '结束', + 'engine.studio.auto.savedDraft': '自动化「{label}」已存为草稿', + 'engine.studio.auto.defaultOff': '默认停用 · 审阅后再启用', + 'engine.studio.auto.heading': '自动化', + 'engine.studio.auto.on': '已启用', + 'engine.studio.auto.off': '已停用', + 'engine.studio.auto.offTitle': '已停用 — 不会运行', + 'engine.studio.auto.onBound': '已启用 · 已绑定触发器', + 'engine.studio.auto.onUnbound': '已启用 · 无触发器(手动运行)', + 'engine.studio.auto.notRunning': '未在此运行', + 'engine.studio.auto.notRunningTitle': '已启用,但它的触发器在此部署中未生效', + 'engine.studio.auto.unpublishedTitle': '尚未部署 — 发布后才会部署', + 'engine.studio.auto.refused': '引擎拒绝注册', + 'engine.studio.auto.unregisteredTitle': '此部署的自动化引擎未注册该流程', + 'engine.studio.auto.enabled': '已启用', + 'engine.studio.auto.disabled': '已停用', + 'engine.studio.auto.enableTitle': '启用此自动化(发布后生效)', + 'engine.studio.auto.disableTitle': '停用此自动化(发布后生效)', + 'engine.studio.auto.enabledToast': '已设为启用 — 发布后生效', + 'engine.studio.auto.disabledToast': '已设为停用 — 发布后生效', + 'engine.studio.auto.newTitle': '新建自动化', + 'engine.studio.auto.none': '还没有自动化 — 点上方的「+」新建', + 'engine.studio.auto.emptyMain': '这个软件包还没有自动化。', + 'engine.studio.auto.namePlaceholder': '名称(如:录用通知)', + 'engine.studio.auto.idPlaceholder': '标识符(如:offer_notice)', + 'engine.studio.auto.presets': '何时运行', + 'engine.studio.auto.preset.created': '新建记录时', + 'engine.studio.auto.preset.updated': '更新记录时', + 'engine.studio.auto.preset.deleted': '删除记录时', + 'engine.studio.auto.preset.manual': '由按钮或其他自动化启动时', + 'engine.studio.auto.preset.needsObject': '请选择它监视的对象。', + 'engine.studio.auto.canvasHint': '点选画布上的节点即可配置', + 'engine.studio.auto.config': '配置', + 'engine.studio.auto.emptyLine1': '在画布里点选一个节点,', + 'engine.studio.auto.emptyLine2': '它的配置会在这里显示。', + 'engine.studio.auto.designersMissing': '本次会话没有注册任何元数据设计器,因此无法在这里设计该流程。', + 'engine.studio.auto.group.noObject': '无对象', + 'engine.studio.auto.kind.record': '记录触发', + 'engine.studio.auto.kind.scheduled': '定时', + 'engine.studio.auto.kind.screen': '交互页面', + 'engine.studio.auto.kind.autolaunched': '自动启动', + 'engine.studio.auto.legend': '图例', + // Access pillar + 'engine.studio.access.created': '权限集「{label}」已创建', + 'engine.studio.access.title': '权限矩阵', + 'engine.studio.access.subtitle': '对象 × CRUD · 字段级读写', + 'engine.studio.access.bannerTitle': + '此矩阵仅列出本包声明的对象,「Save」只合并本包切片 —— 其他包贡献的授权原样保留。编辑保存为软件包草稿,点击顶栏「发布」后随整个包一起生效(与数据、界面一致)。', + 'engine.studio.access.banner': '仅本包对象 · 保存为草稿', + 'engine.studio.access.bannerTitleReadOnly': + '此矩阵仅列出本包声明的对象。本软件包为只读:其权限集可在此查看,但不能修改。', + // ADR-0090 D5/D9 — 待确认的受众绑定建议(isDefault 权限集,管理员确认后生效,服务端绝不自动绑定) + 'engine.studio.access.suggestPromptEveryone': '此包建议将「{set}」授予所有已登录用户(Everyone 岗位)。', + 'engine.studio.access.suggestPromptGuest': '此包建议将「{set}」授予未登录访客(Guest 岗位)。', + 'engine.studio.access.suggestConfirm': '授予', + 'engine.studio.access.suggestConfirming': '授予中…', + 'engine.studio.access.suggestDismiss': '忽略', + 'engine.studio.access.suggestConfirmedToast': '「{set}」已授予 {anchor}。', + 'engine.studio.access.suggestDismissedToast': '已忽略「{set}」的建议。', + // objectstack#22801 → A — accepted / not-in-force / dismissed states and revoke. + 'engine.studio.access.suggestGuestUnsupported': '此处无法授予:Guest 岗位的权限集不由已接受的建议派生。', + 'engine.studio.access.suggestAcceptedState': '「{set}」已为所有已登录用户(Everyone 岗位)接受。', + 'engine.studio.access.suggestNotInForceState': '「{set}」已接受,但尚未生效:本次请求未改变 Everyone 岗位。原因见服务器日志。', + 'engine.studio.access.suggestDismissedState': '「{set}」未授予:该建议已被忽略或撤销。', + 'engine.studio.access.suggestRevoke': '撤销', + 'engine.studio.access.suggestRevoking': '撤销中…', + 'engine.studio.access.suggestRevokedToast': '已撤销对「{set}」的接受。', + 'engine.studio.access.heading': '权限集', + 'engine.studio.access.search': '搜索权限…', + 'engine.studio.access.none': '还没有权限集 — 点上方的「+」新建', + 'engine.studio.access.labelPlaceholder': '显示名(如:销售权限)', + 'engine.studio.access.idPlaceholder': '标识符(如:sales_perms)', + 'engine.studio.access.new': '新建权限集', + 'engine.studio.access.emptyMain': '这个软件包还没有权限集。', + 'engine.studio.access.tab.objects': '对象', + 'engine.studio.access.tab.capabilities': '系统能力', + 'engine.studio.access.tab.rls': '行级安全', + 'engine.studio.access.tab.tabs': '标签页可见性', + 'engine.studio.access.tab.admin': '委派管理', + // zh has one plural form, so each `…One` / `…Other` pair carries the same row. + 'engine.studio.access.grantedOne': '已授权 {count} 个对象', + 'engine.studio.access.grantedOther': '已授权 {count} 个对象', + 'engine.studio.access.overridesOne': '{count} 个字段覆盖', + 'engine.studio.access.overridesOther': '{count} 个字段覆盖', + 'engine.studio.access.objectCountOne': '{count} 个对象', + 'engine.studio.access.objectCountOther': '{count} 个对象', + 'engine.studio.access.objectCountFiltered': '{count} 个对象中的 {shown} 个', + 'engine.studio.access.more': '更多操作', + 'engine.studio.access.colOwd': 'OWD', + 'engine.studio.access.legend.create': '创建', + 'engine.studio.access.legend.read': '读取', + 'engine.studio.access.legend.edit': '编辑', + 'engine.studio.access.legend.delete': '删除', + 'engine.studio.access.legend.transfer': '转移', + 'engine.studio.access.legend.viewAll': '查看全部', + 'engine.studio.access.legend.modifyAll': '修改全部', + 'engine.studio.access.bulkHint': '批量操作(R · CRUD · All · None)显示在指针所在或获得键盘焦点的行上。', + // ── 访问解释面板(ADR-0090 D6 —「为什么该用户可以访问?」)──────────── + 'engine.studio.access.explain.open': '解释访问权限', + 'engine.studio.access.explain.title': '为什么该用户可以访问?', + 'engine.studio.access.explain.description': + '与运行时强制执行走同一条评估管线,逐层报告各层的贡献 —— 权限集 → 岗位 → 绑定链,以及 OWD/共享判定。', + 'engine.studio.access.explain.user': '用户', + 'engine.studio.access.explain.self': '我(当前调用者)', + 'engine.studio.access.explain.pickUser': '选择用户…', + 'engine.studio.access.explain.clearUser': '重置为我', + 'engine.studio.access.explain.pickUserTitle': '选择要解释的用户', + 'engine.studio.access.explain.object': '对象', + 'engine.studio.access.explain.objectPlaceholder': '对象 API 名称', + 'engine.studio.access.explain.objectSelect': '选择对象…', + 'engine.studio.access.explain.operation': '操作', + 'engine.studio.access.explain.run': '解释', + 'engine.studio.access.explain.running': '解释中…', + 'engine.studio.access.explain.allowedLine': '该主体对「{object}」执行 {operation} 将被【允许】。', + 'engine.studio.access.explain.deniedLine': '该主体对「{object}」执行 {operation} 将被【拒绝】。', + 'engine.studio.access.explain.principal': '主体', + 'engine.studio.access.explain.positions': '岗位', + 'engine.studio.access.explain.permissionSets': '权限集', + 'engine.studio.access.explain.layers': '评估管线', + 'engine.studio.access.explain.contributors': '贡献来源', + 'engine.studio.access.explain.readFilter': '合成的行过滤器(读取)', + 'engine.studio.access.explain.empty': '选择对象和操作,然后点击「解释」。', + 'engine.studio.access.explain.failed': '解释失败', + 'engine.studio.access.explain.forbidden': + '无法解释该用户 —— 需要 manage_users 能力,或一个覆盖该用户的委托管理范围(ADR-0090 D6/D12)。', + 'engine.studio.access.explain.layer.principal': '主体解析', + 'engine.studio.access.explain.layer.required_permissions': '前置能力', + 'engine.studio.access.explain.layer.object_crud': '对象 CRUD 授权', + 'engine.studio.access.explain.layer.fls': '字段级安全', + 'engine.studio.access.explain.layer.owd_baseline': '记录基线(OWD)', + 'engine.studio.access.explain.layer.depth': '范围深度', + 'engine.studio.access.explain.layer.sharing': '共享', + 'engine.studio.access.explain.layer.vama_bypass': '查看/修改全部数据旁路', + 'engine.studio.access.explain.layer.rls': '行级安全', + 'engine.studio.access.explain.verdict.grants': '授予', + 'engine.studio.access.explain.verdict.denies': '拒绝', + 'engine.studio.access.explain.verdict.narrows': '收窄', + 'engine.studio.access.explain.verdict.widens': '放宽', + 'engine.studio.access.explain.verdict.neutral': '中性', + 'engine.studio.access.explain.verdict.not_applicable': '不适用', + 'engine.studio.access.explain.op.read': '读取', + 'engine.studio.access.explain.op.create': '新建', + 'engine.studio.access.explain.op.update': '更新', + 'engine.studio.access.explain.op.delete': '删除', + 'engine.studio.access.explain.op.transfer': '转移', + 'engine.studio.access.explain.op.restore': '恢复', + 'engine.studio.access.explain.op.purge': '清除', + // [C2 / ADR-0095] 记录级解释 + 'engine.studio.access.explain.record': '记录(可选)', + 'engine.studio.access.explain.recordPlaceholder': '记录 id — 解释某一条具体记录', + 'engine.studio.access.explain.pickRecord': '选择', + 'engine.studio.access.explain.clearRecord': '清除记录', + 'engine.studio.access.explain.pickRecordTitle': '选择要解释的记录', + 'engine.studio.access.explain.recordVisibleLine': '记录 {recordId} 对该操作【可见】。', + 'engine.studio.access.explain.recordHiddenLine': '记录 {recordId} 对该操作【不可见】。', + 'engine.studio.access.explain.decidedBy': '决定层', + 'engine.studio.access.explain.postureHint': '该主体被评估时所处的 posture 档位(ADR-0095 D2)。', + 'engine.studio.access.explain.posture.PLATFORM_ADMIN': '平台管理员', + 'engine.studio.access.explain.posture.TENANT_ADMIN': '租户管理员', + 'engine.studio.access.explain.posture.MEMBER': '成员', + 'engine.studio.access.explain.posture.EXTERNAL': '外部用户', + 'engine.studio.access.explain.layer.tenant_isolation': '租户隔离(Layer 0)', + 'engine.studio.access.explain.kernelTier.layer_0_tenant': '租户墙', + 'engine.studio.access.explain.kernelTier.layer_1_business': '业务 RLS', + 'engine.studio.access.explain.kernelTierHint': 'Layer 0 = 永远最先的组织墙;Layer 1 = 业务行级安全。', + 'engine.studio.access.explain.recordOutcome': '该记录', + 'engine.studio.access.explain.outcome.admitted': '准入', + 'engine.studio.access.explain.outcome.excluded': '排除', + 'engine.studio.access.explain.outcome.not_evaluated': '未评估', + 'engine.studio.access.explain.matches': '记录匹配', + 'engine.studio.access.explain.noMatch': '记录不匹配', + 'engine.studio.access.explain.effectiveRowFilter': '有效行过滤', + 'engine.studio.access.explain.ruleKind.tenant_filter': '租户过滤', + 'engine.studio.access.explain.ruleKind.owd_baseline': 'OWD 基线', + 'engine.studio.access.explain.ruleKind.ownership': '归属', + 'engine.studio.access.explain.ruleKind.record_share': '记录共享', + 'engine.studio.access.explain.ruleKind.sharing_rule': '共享规则', + 'engine.studio.access.explain.ruleKind.team': '团队', + 'engine.studio.access.explain.ruleKind.territory': '区域', + 'engine.studio.access.explain.ruleKind.rls_policy': 'RLS 策略', }; export default STUDIO_ZH; From 7c7bbb8791346cfca6d6308bf6849ff73f406659 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 20:08:33 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test(ui):=20S4=20designer=20rows=20read=20f?= =?UTF-8?q?rom=20the=20studio=20namespace=20=E2=80=94=20pin=20widened,=20z?= =?UTF-8?q?h=20preloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pin gains a floor for each of the eleven S4 families. Seven dom suites and one unit suite that read moved S4 zh rows synchronously make the zh catalogue resident first; the sweep ran every app-shell test naming zh with the zh loader delayed 4000 ms, and each unit-project test alone. AddFieldPopover's lazy-namespace test takes engine.studio.cancel as its table key now that engine.studio.data.addField moved, and block-config-i18n holds its 3,000-key floor over both homes together. Claude-Session: https://claude.ai/code/session_01JQwxbX9LnJhjTQPJEmoBYi Co-authored-by: Claude --- .../25922-studio-i18n-pillars-datasources.md | 10 +++++++++ ...tasourceResourcePage.locale-13460.test.tsx | 6 ++++++ ...ectorStrings.studioNamespace-25922.test.ts | 21 +++++++++++++++---- .../__tests__/block-config-i18n.test.ts | 9 ++++++-- .../adminPreviews.i18n-10862-s2.test.tsx | 6 ++++++ ...rnalDatasourcePanel.i18n-10862-s3.test.tsx | 6 ++++++ .../federationTabs.i18n-10862-s4.test.tsx | 6 ++++++ ...AutomationCopy.plainLanguage-11659.test.ts | 7 +++++++ ...ieldPopover.studioNamespace-25922.test.tsx | 6 +++--- ...ObjectFormDesigner.typeName-13148.test.tsx | 6 ++++++ .../StudioDesignSurface.emptyPackage.test.tsx | 6 ++++++ .../StudioDesignSurface.railLabels.test.tsx | 6 ++++++ 12 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 .changeset/25922-studio-i18n-pillars-datasources.md diff --git a/.changeset/25922-studio-i18n-pillars-datasources.md b/.changeset/25922-studio-i18n-pillars-datasources.md new file mode 100644 index 00000000000..eb876a9ce2f --- /dev/null +++ b/.changeset/25922-studio-i18n-pillars-datasources.md @@ -0,0 +1,10 @@ +--- +'@object-ui/i18n': patch +--- + +feat(i18n): five Studio pillars, the data source pages and the field types move into the `studio` namespace + +Clause-②: no +Metadata-protocol: none + +- **Fourth slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The keys under `engine.studio.access.*`, `engine.studio.if.*`, `engine.studio.data.*`, `engine.studio.auto.*` and `engine.studio.settings.*`, the data source pages' `engine.datasource.*`, `engine.externalDatasource.*` and `engine.datasourcePreview.*`, and the field types' `engine.fieldType.*`, `engine.fieldTypeDesc.*` and `engine.fieldCategory.*` (549 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/datasource/DatasourceResourcePage.locale-13460.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.locale-13460.test.tsx index 54b4ef90830..76250ae3d9a 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.locale-13460.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.locale-13460.test.tsx @@ -37,6 +37,12 @@ vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); import { t } from '../i18n'; import { DatasourceResourcePage } from './DatasourceResourcePage'; +// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio` +// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922). +// Resident before any case, so the zh rows below read synchronously. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); + afterEach(cleanup); const DRIVER = { id: 'sqlite', label: 'SQLite', configSchema: { properties: {} } }; 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 c9aaab9e761..231bffeef0e 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,10 +1,11 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * objectstack#25922 slices S2 and S3 — the designer rows that moved into - * `@object-ui/i18n`'s `studio` namespace (every `engine.inspector.*` row, and - * `engine.packages.*`, `engine.form.*`, `engine.edit.*`), and app-shell's `t()` - * / `tOptional()` still answer every one of them the way they did when the rows + * objectstack#25922 slices S2 to S4 — 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. * * What would break it: @@ -41,6 +42,18 @@ const FAMILY_FLOOR: Readonly> = Object.freeze({ 'engine.packages.': 120, 'engine.form.': 90, 'engine.edit.': 90, + // S4 — five Studio pillars, the data source pages, the field types + 'engine.studio.access.': 120, + 'engine.studio.if.': 50, + 'engine.studio.data.': 45, + 'engine.studio.auto.': 40, + 'engine.studio.settings.': 35, + 'engine.datasource.': 50, + 'engine.externalDatasource.': 45, + 'engine.datasourcePreview.': 10, + 'engine.fieldType.': 45, + 'engine.fieldTypeDesc.': 45, + 'engine.fieldCategory.': 9, }); /** diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts b/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts index ef5bbb99ed0..b1bacc77e36 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts @@ -678,9 +678,12 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { // FEWER keys — which would make every assertion below vacuously green over // an empty corpus. Ranges first: overlapping ones would mean the `};` // scan ran past a table and both sets became the same set. + // The table drains slice by slice into the namespace (objectstack#25922), + // so its floor is "non-empty"; the 3,000-key floor is held over both homes + // together, below. expect(EN_TABLE.to).toBeLessThan(ZH_TABLE.from); - expect(EN_TABLE.keys.length).toBeGreaterThan(1500); - expect(ZH_TABLE.keys.length).toBeGreaterThan(1500); + expect(EN_TABLE.keys.length).toBeGreaterThan(0); + expect(ZH_TABLE.keys.length).toBeGreaterThan(0); // No duplicates: a key declared twice would be a silent overwrite, and the // set arithmetic below would hide it. expect(EN_TABLE.keys.length).toBe(new Set(EN_TABLE.keys).size); @@ -691,6 +694,8 @@ describe('a retired key leaves BOTH locale tables (objectui#8368)', () => { expect(ZH_HOME.keys.length).toBeGreaterThan(150); expect(EN_KEYS.keys.length).toBe(new Set(EN_KEYS.keys).size); expect(ZH_KEYS.keys.length).toBe(new Set(ZH_KEYS.keys).size); + expect(EN_KEYS.keys.length).toBeGreaterThan(3000); + expect(ZH_KEYS.keys.length).toBeGreaterThan(3000); }); it('the extractor really sees the keys the derivation implies', () => { diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/adminPreviews.i18n-10862-s2.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/adminPreviews.i18n-10862-s2.test.tsx index 26a8c8eccd4..653c79f8baa 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/adminPreviews.i18n-10862-s2.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/adminPreviews.i18n-10862-s2.test.tsx @@ -44,6 +44,12 @@ import { PermissionPreview } from './PermissionPreview'; import { PositionPreview } from './PositionPreview'; import { TranslationPreview } from './TranslationPreview'; +// 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 Lang = 'en' | 'zh'; diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/externalDatasourcePanel.i18n-10862-s3.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/externalDatasourcePanel.i18n-10862-s3.test.tsx index b3dc82aa6e4..f06b8ea0451 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/externalDatasourcePanel.i18n-10862-s3.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/externalDatasourcePanel.i18n-10862-s3.test.tsx @@ -51,6 +51,12 @@ vi.mock('../external/api', async (importOriginal) => { import { t, tFormat } from '../i18n'; import { DatasourcePreview } from './DatasourcePreview'; +// 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.refresh = 'ok'; diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/federationTabs.i18n-10862-s4.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/federationTabs.i18n-10862-s4.test.tsx index 26fc0eb9840..ea9d30cd02e 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/federationTabs.i18n-10862-s4.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/federationTabs.i18n-10862-s4.test.tsx @@ -73,6 +73,12 @@ vi.mock('../external/api', async (importOriginal) => { import { t, tFormat } from '../i18n'; import { DatasourcePreview } from './DatasourcePreview'; +// 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.tables = []; diff --git a/ui/packages/app-shell/src/views/metadata-admin/studioAutomationCopy.plainLanguage-11659.test.ts b/ui/packages/app-shell/src/views/metadata-admin/studioAutomationCopy.plainLanguage-11659.test.ts index cfc23402530..649c47ec844 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/studioAutomationCopy.plainLanguage-11659.test.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/studioAutomationCopy.plainLanguage-11659.test.ts @@ -19,6 +19,13 @@ import { describe, expect, it } from 'vitest'; import { t } from './i18n'; +// `engine.studio.auto.*` lives in `@object-ui/i18n`'s `studio` namespace, whose +// `zh` catalogue is fetched on demand (objectstack#25922). Resident before any +// case: this file runs in the `unit` project, whose shared module graph would +// otherwise let it pass only when another file had loaded `zh` first. +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); + /** The automation pillar's chrome: every `engine.studio.auto.*` row it renders as prose. */ const PILLAR_ROWS = [ 'engine.studio.auto.heading', 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 f859e50d6c4..3ed2ac3a085 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 @@ -6,8 +6,8 @@ * * 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 title and buttons (`engine.studio.*`) are still in the - * app-shell table. It takes its locale from `useMetadataLocale()`, the hook + * 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: * * - zh: the namespace's `zh` catalogue is NOT resident when the screen first @@ -40,7 +40,7 @@ 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.data.addField'; +const TABLE = 'engine.studio.cancel'; function Screen({ lang }: { lang: string }) { return ( diff --git a/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.typeName-13148.test.tsx b/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.typeName-13148.test.tsx index cbfe845219d..371f988ca96 100644 --- a/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.typeName-13148.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.typeName-13148.test.tsx @@ -34,6 +34,12 @@ import { FIELD_TYPE_META, type FieldTypeId } from '../metadata-admin/previews/fi import { fieldTypeName } from '../metadata-admin/previews/fieldTypeName'; 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'); + afterEach(cleanup); type Lang = 'en' | 'zh'; diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.emptyPackage.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.emptyPackage.test.tsx index b5b6038670a..c82d846a6c8 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.emptyPackage.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.emptyPackage.test.tsx @@ -53,6 +53,12 @@ vi.mock('@object-ui/react', async (importOriginal) => { import { DataPillar } from './StudioDesignSurface'; import { t } 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 renderPillar(props: Partial> = {}) { diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.railLabels.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.railLabels.test.tsx index 1eb95157982..e63274eaa61 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.railLabels.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.railLabels.test.tsx @@ -67,6 +67,12 @@ import { InterfacesPillar, StudioNavItemInspector } from './StudioDesignSurface' import { t } from '../metadata-admin/i18n'; import { listStudioCanvasPreviewTypes } from './studio-canvas-preview'; +// 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'); + // ── StudioDesignSurface.railLabelInherits-11196 ────────────────────────────── /**