diff --git a/.changeset/adr-0029-d8-i18n-plugin-ownership.md b/.changeset/adr-0029-d8-i18n-plugin-ownership.md new file mode 100644 index 000000000..6a4a040d3 --- /dev/null +++ b/.changeset/adr-0029-d8-i18n-plugin-ownership.md @@ -0,0 +1,33 @@ +--- +"@objectstack/platform-objects": patch +"@objectstack/plugin-webhooks": patch +"@objectstack/plugin-approvals": patch +"@objectstack/plugin-security": patch +"@objectstack/plugin-sharing": patch +--- + +ADR-0029 D8 — migrate i18n ownership for the moved domains to their plugins. + +The object translations for the domains decomposed in K2.a/K2.b/K2 previously +lived in the `@objectstack/platform-objects` generated bundles even though the +objects now live in their capability plugins. This moves each domain's i18n +extraction + bundles to the owning plugin, preserving every hand-translated +string (zh-CN / ja-JP / es-ES): + +- Each plugin gains a build-time `scripts/i18n-extract.config.ts` and a + `src/translations/` bundle (`{locale}.objects.generated.ts` + an `index.ts` + barrel), generated with `os i18n extract` and self-baselined so re-runs + preserve translations. +- Each plugin loads its bundle at runtime on `kernel:ready` via + `i18n.loadTranslations` (the i18n service is optional — load is best-effort). + - `plugin-webhooks` ← `sys_webhook`, `sys_webhook_delivery` + - `plugin-approvals` ← `sys_approval_request`, `sys_approval_action` + - `plugin-security` ← `sys_role`, `sys_permission_set`, + `sys_user_permission_set`, `sys_role_permission_set` + - `plugin-sharing` ← `sys_record_share`, `sys_sharing_rule`, `sys_share_link` +- `@objectstack/platform-objects` translation bundles are regenerated to drop + those objects' keys (its extract config already excluded them); all other + objects' translations and the metadata-form bundles are preserved. + +Net runtime effect is unchanged (same translations load, now contributed by the +package that owns each object) — closing the D8 follow-up tracked since K2.a. diff --git a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts index dc5dbcd07..8b7370cd0 100644 --- a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts @@ -60,18 +60,123 @@ export const enMetadataForms: NonNullable = { fields: { helpText: "Add the columns this object will store" }, - "fields.name": { - helpText: "snake_case identifier" - }, "fields.label": { helpText: "Display label" }, "fields.type": { helpText: "Field type" }, + "fields.description": { + helpText: "Developer documentation for this column" + }, + "fields.required": { + helpText: "Must be set on every record" + }, + "fields.unique": { + helpText: "Disallow duplicate values" + }, + "fields.indexed": { + helpText: "Create a database index for faster querying" + }, + "fields.readonly": { + helpText: "Visible but never user-editable" + }, + "fields.immutable": { + helpText: "Editable on create, locked thereafter" + }, + "fields.hidden": { + helpText: "Hidden from default UI" + }, + "fields.searchable": { + helpText: "Include in full-text search" + }, + "fields.sortable": { + helpText: "Allow sorting on this column" + }, + "fields.filterable": { + helpText: "Allow filtering on this column" + }, + "fields.defaultValue": { + helpText: "Default value for new records (JSON literal)" + }, + "fields.placeholder": { + helpText: "Placeholder hint" + }, + "fields.maxLength": { + helpText: "Max characters" + }, + "fields.minLength": { + helpText: "Min characters" + }, + "fields.min": { + helpText: "Minimum value" + }, + "fields.max": { + helpText: "Maximum value" + }, + "fields.precision": { + helpText: "Total digits" + }, + "fields.scale": { + helpText: "Decimal places" + }, + "fields.options": { + helpText: "Available choices" + }, + "fields.options.icon": { + helpText: "Lucide icon name" + }, "fields.reference": { helpText: "Target object (for lookup/master_detail)" }, + "fields.referenceFilter": { + helpText: "CEL filter applied to the picker" + }, + "fields.cascadeDelete": { + helpText: "Delete children when parent is deleted" + }, + "fields.multiple": { + helpText: "Allow selecting multiple records" + }, + "fields.formula": { + helpText: "CEL formula expression" + }, + "fields.returnType": { + helpText: "Result type for formulas" + }, + "fields.summaryType": { + helpText: "Aggregation" + }, + "fields.summaryField": { + helpText: "Field on child object to aggregate" + }, + "fields.displayFormat": { + helpText: "e.g. \"INV-{0000}\"" + }, + "fields.startingNumber": { + helpText: "Starting sequence value" + }, + "fields.language": { + helpText: "Editor language (e.g. sql, javascript)" + }, + "fields.validation": { + helpText: "CEL predicate — must evaluate true" + }, + "fields.errorMessage": { + helpText: "Shown when validation fails" + }, + "fields.audit": { + helpText: "Audit changes to this field" + }, + "fields.trackHistory": { + helpText: "Keep change history" + }, + "fields.pii": { + helpText: "Personally identifiable information" + }, + "fields.encrypted": { + helpText: "Encrypt at rest" + }, capabilities: { helpText: "Enable/disable system features" }, @@ -742,58 +847,15 @@ export const enMetadataForms: NonNullable = { } } }, - workflow: { - label: "Workflow", - sections: { - basics: { - label: "Basics", - description: "Identity and the object/event that triggers it." - }, - actions: { - label: "Actions", - description: "What this workflow does when fired." - }, - advanced: { - label: "Advanced", - description: "Ordering and execution behaviour." - } - }, - fields: { - name: { - helpText: "Unique identifier (snake_case)" - }, - objectName: { - helpText: "Which object triggers this workflow" - }, - triggerType: { - helpText: "When to run: on_create, on_update, on_delete, schedule" - }, - active: { - helpText: "Enable/disable this workflow" - }, - description: { - helpText: "What this workflow does" - }, - criteria: { - helpText: "CEL expression: only run when this condition is true" - }, - actions: { - helpText: "Actions to execute immediately (field update, email, API call, etc.)" - }, - timeTriggers: { - helpText: "Scheduled actions (e.g., send reminder 1 day before deadline)" - }, - executionOrder: { - helpText: "Run order when multiple workflows match (lower = earlier)" - } - } - }, job: { label: "Background Job" }, datasource: { label: "Datasource" }, + external_catalog: { + label: "External Catalog" + }, translation: { label: "Translation" }, @@ -817,27 +879,47 @@ export const enMetadataForms: NonNullable = { label: "Subject", description: "Subject line. Supports {{var.path}} interpolation." }, - body: { - label: "Body", - description: "Email body. Use {{var}} for variables. Editor highlights based on body type." + html_body: { + label: "HTML body", + description: "Rich HTML body. Most clients strip , so use inline styles." + }, + plain_text_body: { + label: "Plain-text body", + description: "Optional plain-text alternative. When omitted, the service strips tags from the HTML body to derive one. Providing one improves spam scoring." + }, + variables: { + label: "Variables", + description: "Declared variables. Rendered as hints in Studio and validated by sendTemplate() when required." + }, + delivery_overrides: { + label: "Delivery overrides", + description: "Optional per-template overrides for From / Reply-To." }, - variables_and_attachments: { - label: "Variables & Attachments", - description: "Declared template variables and optional file attachments." + status: { + label: "Status" } }, fields: { - id: { - helpText: "Template id (e.g. auth.password_reset)" + name: { + helpText: "Dotted snake_case (e.g. auth.password_reset, crm.welcome)" }, - body: { - helpText: "Body content. Will be rendered as HTML, plain text, or Markdown based on Body Type." + locale: { + helpText: "BCP-47 tag — e.g. en-US, zh-CN" }, variables: { helpText: "List of variable names referenced in subject/body" }, - attachments: { - helpText: "[{ \"name\": \"...\", \"url\": \"...\" }]" + fromOverride: { + helpText: "{ \"name\": \"Acme Sales\", \"address\": \"sales@acme.com\" }" + }, + replyTo: { + helpText: "Reply-To email address" + }, + active: { + helpText: "When unchecked, sendTemplate() returns TEMPLATE_INACTIVE." + }, + isSystem: { + helpText: "Built-in template; tenants may override but should not delete." } } }, diff --git a/packages/platform-objects/src/apps/translations/en.objects.generated.ts b/packages/platform-objects/src/apps/translations/en.objects.generated.ts index 1132248d0..670858437 100644 --- a/packages/platform-objects/src/apps/translations/en.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.objects.generated.ts @@ -56,6 +56,9 @@ export const enObjects: NonNullable = { } }, _views: { + me: { + label: "My Profile" + }, all_users: { label: "All Users" }, @@ -108,10 +111,28 @@ export const enObjects: NonNullable = { label: "Change Email", successMessage: "Verification email sent — check the new address to confirm." }, + resend_verification_email: { + label: "Resend Verification Email", + successMessage: "Verification email sent — check your inbox." + }, delete_my_account: { label: "Delete My Account", confirmText: "Permanently delete your account? This cannot be undone — all your sessions will be terminated and all data you own will be removed per the configured retention policy.", successMessage: "Account deleted" + }, + enable_two_factor: { + label: "Enable Two-Factor Auth", + successMessage: "Two-factor authentication enabled. Scan the QR code or paste the otpauth URI into your authenticator app, then verify a code to complete setup." + }, + disable_two_factor: { + label: "Disable Two-Factor Auth", + confirmText: "Turn off two-factor authentication? Your account will be less secure.", + successMessage: "Two-factor authentication disabled." + }, + generate_backup_codes: { + label: "Regenerate Backup Codes", + confirmText: "Generate a new set of backup codes? Any previously generated codes will stop working.", + successMessage: "New backup codes generated — save them somewhere safe." } } }, @@ -331,6 +352,11 @@ export const enObjects: NonNullable = { label: "Leave Organization", confirmText: "Leave this organization? You will lose access to all of its resources.", successMessage: "You have left the organization" + }, + change_slug: { + label: "Change Slug", + confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?", + successMessage: "Organization slug changed" } } }, @@ -361,6 +387,11 @@ export const enObjects: NonNullable = { } } }, + _views: { + mine: { + label: "My Memberships" + } + }, _actions: { add_member: { label: "Add Member", @@ -760,6 +791,10 @@ export const enObjects: NonNullable = { backup_codes: { label: "Backup Codes", help: "JSON-serialized backup recovery codes" + }, + verified: { + label: "Verified", + help: "Whether the enrollment was confirmed with a valid TOTP code (managed by better-auth)" } }, _views: { @@ -998,6 +1033,9 @@ export const enObjects: NonNullable = { } }, _views: { + mine: { + label: "My Applications" + }, active: { label: "Active" }, @@ -1183,473 +1221,6 @@ export const enObjects: NonNullable = { } } }, - sys_role: { - label: "Role", - pluralLabel: "Roles", - description: "Role definitions for RBAC access control", - fields: { - label: { - label: "Display Name" - }, - name: { - label: "API Name", - help: "Unique machine name for the role (e.g. admin, editor, viewer)" - }, - description: { - label: "Description" - }, - permissions: { - label: "Permissions", - help: "JSON-serialized array of permission strings" - }, - active: { - label: "Active" - }, - is_default: { - label: "Default Role", - help: "Automatically assigned to new users" - }, - id: { - label: "Role ID" - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - }, - _views: { - active: { - label: "Active" - }, - default_roles: { - label: "Default" - }, - custom: { - label: "Custom" - }, - all_roles: { - label: "All" - } - }, - _actions: { - activate_role: { - label: "Activate Role", - successMessage: "Role activated" - }, - deactivate_role: { - label: "Deactivate Role", - confirmText: "Deactivate this role? Users with the role keep their assignment but the role stops granting permissions until re-activated.", - successMessage: "Role deactivated" - }, - set_default_role: { - label: "Set as Default", - confirmText: "Make this the default role for new users? Existing users are unaffected.", - successMessage: "Default role updated" - }, - clone_role: { - label: "Clone Role", - successMessage: "Role cloned" - } - } - }, - sys_permission_set: { - label: "Permission Set", - pluralLabel: "Permission Sets", - description: "Named permission groupings for fine-grained access control", - fields: { - label: { - label: "Display Name" - }, - name: { - label: "API Name", - help: "Unique machine name for the permission set" - }, - description: { - label: "Description" - }, - object_permissions: { - label: "Object Permissions", - help: "JSON-serialized object-level CRUD permissions" - }, - field_permissions: { - label: "Field Permissions", - help: "JSON-serialized field-level read/write permissions" - }, - system_permissions: { - label: "System Permissions", - help: "JSON-serialized array of system capability names (e.g. [\"setup.access\",\"studio.access\",\"manage_users\"])" - }, - row_level_security: { - label: "Row-Level Security", - help: "JSON-serialized array of row-level security policies (USING/CHECK clauses)" - }, - tab_permissions: { - label: "Tab Permissions", - help: "JSON-serialized map of app tab visibility (visible | hidden | default_on | default_off)" - }, - active: { - label: "Active" - }, - id: { - label: "Permission Set ID" - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - }, - _views: { - active: { - label: "Active" - }, - inactive: { - label: "Inactive" - }, - all_permsets: { - label: "All" - } - }, - _actions: { - activate_permission_set: { - label: "Activate", - successMessage: "Permission set activated" - }, - deactivate_permission_set: { - label: "Deactivate", - confirmText: "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated.", - successMessage: "Permission set deactivated" - }, - clone_permission_set: { - label: "Clone", - successMessage: "Permission set cloned" - } - } - }, - sys_user_permission_set: { - label: "User Permission Set", - pluralLabel: "User Permission Sets", - description: "Direct assignment of a permission set to a user (optionally scoped to an organization).", - fields: { - id: { - label: "Assignment ID", - help: "UUID of the assignment." - }, - user_id: { - label: "User", - help: "Foreign key to sys_user." - }, - permission_set_id: { - label: "Permission Set", - help: "Foreign key to sys_permission_set." - }, - organization_id: { - label: "Organization", - help: "Optional organization scope. NULL = applies in every org context." - }, - granted_by: { - label: "Granted By", - help: "User who granted this permission set." - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - } - }, - sys_role_permission_set: { - label: "Role Permission Set", - pluralLabel: "Role Permission Sets", - description: "Binds a permission set to a role.", - fields: { - id: { - label: "Binding ID", - help: "UUID of the role-permission-set binding." - }, - role_id: { - label: "Role", - help: "Foreign key to sys_role." - }, - permission_set_id: { - label: "Permission Set", - help: "Foreign key to sys_permission_set." - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - } - }, - sys_record_share: { - label: "Record Share", - pluralLabel: "Record Shares", - description: "Per-record sharing grant — extends OWD with explicit access", - fields: { - id: { - label: "Share ID" - }, - object_name: { - label: "Object", - help: "Short object name of the shared record" - }, - record_id: { - label: "Record", - help: "Primary key of the shared record within object_name" - }, - recipient_type: { - label: "Recipient Type", - help: "Kind of principal that holds the grant", - options: { - user: "user", - group: "group", - role: "role", - role_and_subordinates: "role_and_subordinates", - guest: "guest" - } - }, - recipient_id: { - label: "Recipient", - help: "ID of the user/group/role that receives access" - }, - access_level: { - label: "Access Level", - help: "What the recipient can do — read | edit | full (transfer/share/delete)", - options: { - read: "read", - edit: "edit", - full: "full" - } - }, - source: { - label: "Source", - help: "Why this grant exists — used by the rule evaluator to reconcile", - options: { - manual: "manual", - rule: "rule", - team: "team", - inherited: "inherited" - } - }, - source_id: { - label: "Source ID", - help: "Rule name / team id when source != manual" - }, - granted_by: { - label: "Granted By", - help: "User that created the grant (manual only)" - }, - reason: { - label: "Reason", - help: "Optional free-text explanation surfaced to the recipient" - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - }, - _views: { - granted_to_me: { - label: "Granted to Me" - }, - granted_by_me: { - label: "Granted by Me" - }, - by_object: { - label: "By Object" - }, - manual_grants: { - label: "Manual Grants" - }, - rule_grants: { - label: "Rule Grants" - }, - all_shares: { - label: "All" - } - } - }, - sys_sharing_rule: { - label: "Sharing Rule", - pluralLabel: "Sharing Rules", - description: "Declarative sharing rule that auto-materialises sys_record_share grants. Authored via defineSharingRule() in code or the Studio criteria builder.", - fields: { - id: { - label: "Rule ID" - }, - organization_id: { - label: "Organization", - help: "Tenant that owns this rule; null = global" - }, - name: { - label: "Name", - help: "Unique snake_case rule name" - }, - label: { - label: "Display Label" - }, - description: { - label: "Description" - }, - object_name: { - label: "Object", - help: "Short object name (e.g. opportunity, account)" - }, - criteria_json: { - label: "Criteria (FilterCondition JSON)", - help: "JSON FilterCondition matched against records of object_name. Empty = match all." - }, - recipient_type: { - label: "Recipient Type", - help: "Kind of principal that receives access — expanded to user grants at evaluation time. `department` walks the parent_department_id tree; `team` is flat (better-auth).", - options: { - user: "user", - team: "team", - department: "department", - role: "role", - queue: "queue" - } - }, - recipient_id: { - label: "Recipient", - help: "department id / team id / role name / queue name / user id depending on recipient_type" - }, - access_level: { - label: "Access Level", - options: { - read: "read", - edit: "edit", - full: "full" - } - }, - active: { - label: "Active", - help: "Only active rules participate in lifecycle evaluation" - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - }, - _views: { - active: { - label: "Active" - }, - inactive: { - label: "Inactive" - }, - by_object: { - label: "By Object" - }, - all_rules: { - label: "All" - } - } - }, - sys_share_link: { - label: "Share Link", - pluralLabel: "Share Links", - description: "Opaque capability token granting access to a single record. Notion/Figma-style public link sharing.", - fields: { - id: { - label: "Link ID" - }, - token: { - label: "Token", - help: "Opaque URL-safe random token (≥ 22 chars). The only secret in this row." - }, - object_name: { - label: "Object", - help: "Short object name of the shared record (e.g. ai_conversation, contracts_contract)" - }, - record_id: { - label: "Record", - help: "Primary key of the shared record within object_name" - }, - permission: { - label: "Permission", - help: "What the link holder can do with the record", - options: { - view: "View", - comment: "Comment", - edit: "Edit" - } - }, - audience: { - label: "Audience", - help: "Gating layer applied on top of the token check", - options: { - public: "Public (indexable)", - link_only: "Anyone with the link", - signed_in: "Signed-in users", - email: "Specific emails" - } - }, - expires_at: { - label: "Expires At", - help: "When set, resolveToken returns null after this timestamp" - }, - email_allowlist: { - label: "Email Allowlist", - help: "Lowercased addresses checked when audience=email" - }, - password_hash: { - label: "Password Hash", - help: "Argon2/bcrypt hash. When set, the UI prompts for a password before rendering." - }, - redact_fields: { - label: "Per-Link Redactions", - help: "Extra fields stripped from the response, on top of the object-default set" - }, - label: { - label: "Label", - help: "Free-text shown in the share dialog (e.g. \"ACME Q3 contract\")" - }, - revoked_at: { - label: "Revoked At", - help: "When set, the link is permanently disabled" - }, - created_by: { - label: "Created By", - help: "Issuer of the link" - }, - created_at: { - label: "Created At" - }, - last_used_at: { - label: "Last Used At", - help: "Stamped by resolveToken; used by the dashboard to highlight active links" - }, - use_count: { - label: "Use Count", - help: "Incremented by resolveToken on every successful resolution" - } - }, - _views: { - active_links: { - label: "Active" - }, - by_me: { - label: "Created by Me" - }, - revoked: { - label: "Revoked" - }, - all_links: { - label: "All" - } - } - }, sys_audit_log: { label: "Audit Log", pluralLabel: "Audit Logs", @@ -1979,28 +1550,26 @@ export const enObjects: NonNullable = { id: { label: "Notification ID" }, - recipient_id: { - label: "Recipient", - help: "User the notification is delivered to" + topic: { + label: "Topic", + help: "Notification topic, e.g. task.assigned, collab.mention" }, - type: { - label: "Type", - help: "Notification category — drives icon + sort priority", + payload: { + label: "Payload", + help: "Template inputs carried to channels (title/body/url/actor/source/…)" + }, + severity: { + label: "Severity", + help: "Severity hint for rendering / filtering", options: { - mention: "mention", - assignment: "assignment", - comment_reply: "comment_reply", - lead_converted: "lead_converted", - task_due: "task_due", - system: "system" + info: "info", + warning: "warning", + critical: "critical" } }, - title: { - label: "Title" - }, - body: { - label: "Body", - help: "Optional secondary text (one-line summary)" + dedup_key: { + label: "Dedup Key", + help: "Idempotency key within a topic window; a repeat emit is a no-op" }, source_object: { label: "Source Object", @@ -2010,50 +1579,20 @@ export const enObjects: NonNullable = { label: "Source Record", help: "Record id within source_object" }, - url: { - label: "Deep Link", - help: "Optional URL to navigate to when clicked" - }, actor_id: { label: "Actor", help: "User who caused the notification (mentioner, assigner)" }, - actor_name: { - label: "Actor Name" - }, - is_read: { - label: "Read", - help: "True once recipient acknowledges" - }, - read_at: { - label: "Read At" - }, created_at: { label: "Created At" - }, - updated_at: { - label: "Updated At" } }, _views: { - unread: { - label: "Unread" - }, - mine: { - label: "Mine" - }, - all_notifications: { - label: "All" - } - }, - _actions: { - mark_read: { - label: "Mark as Read", - successMessage: "Notification marked as read" + recent: { + label: "Recent" }, - mark_unread: { - label: "Mark as Unread", - successMessage: "Notification marked as unread" + by_topic: { + label: "By Topic" } } }, @@ -2331,142 +1870,6 @@ export const enObjects: NonNullable = { } } }, - sys_approval_request: { - label: "Approval Request", - pluralLabel: "Approval Requests", - description: "Live approval instance tracked per submission", - fields: { - id: { - label: "Request ID" - }, - organization_id: { - label: "Organization", - help: "Tenant that owns this approval request (propagated from submitter context)" - }, - process_name: { - label: "Source", - help: "Origin of the request — `flow:` for node-driven approvals" - }, - object_name: { - label: "Object" - }, - record_id: { - label: "Record ID" - }, - submitter_id: { - label: "Submitter" - }, - submitter_comment: { - label: "Submitter Comment" - }, - status: { - label: "Status", - help: "Lifecycle state of the request", - options: { - pending: "pending", - approved: "approved", - rejected: "rejected", - recalled: "recalled" - } - }, - current_step: { - label: "Current Step", - help: "Machine name of the step awaiting approval" - }, - current_step_index: { - label: "Current Step Index" - }, - pending_approvers: { - label: "Pending Approvers", - help: "Comma-separated user ids who can act on the current step" - }, - payload_json: { - label: "Snapshot", - help: "Record snapshot at submission time" - }, - process_hash: { - label: "Process Hash", - help: "sha256 of the approval process body at submit time (ADR-0009 execution pinning). Resolved through sys_metadata_history so process upgrades do not affect in-flight requests." - }, - completed_at: { - label: "Completed At" - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - }, - _views: { - my_pending: { - label: "My Pending" - }, - submitted_by_me: { - label: "I Submitted" - }, - completed: { - label: "Completed" - }, - all_requests: { - label: "All" - } - } - }, - sys_approval_action: { - label: "Approval Action", - pluralLabel: "Approval Actions", - description: "Append-only audit trail for approval actions", - fields: { - id: { - label: "Action ID" - }, - organization_id: { - label: "Organization", - help: "Tenant that owns this action (mirrors the parent request)" - }, - request_id: { - label: "Request" - }, - step_name: { - label: "Step", - help: "Machine name of the step at the time of the action" - }, - step_index: { - label: "Step Index" - }, - action: { - label: "Action", - options: { - submit: "submit", - approve: "approve", - reject: "reject", - recall: "recall", - escalate: "escalate" - } - }, - actor_id: { - label: "Actor" - }, - comment: { - label: "Comment" - }, - created_at: { - label: "Created At" - } - }, - _views: { - recent: { - label: "Recent" - }, - by_actor: { - label: "By Actor" - }, - all_actions: { - label: "All" - } - } - }, sys_job: { label: "Background Job", pluralLabel: "Background Jobs", @@ -2660,70 +2063,6 @@ export const enObjects: NonNullable = { } } }, - sys_webhook: { - label: "Webhook", - pluralLabel: "Webhooks", - description: "Outbound HTTP webhook subscription. Authored via defineWebhook() in code or the Studio editor; executed by the HTTP connector plugin.", - fields: { - id: { - label: "Webhook ID" - }, - name: { - label: "Name", - help: "Unique snake_case name — referenced in logs and audit" - }, - label: { - label: "Display Label" - }, - object_name: { - label: "Object", - help: "Short object name whose events fire this webhook (blank = manual / API-triggered)" - }, - triggers: { - label: "Triggers", - help: "Comma-separated event list: create,update,delete,undelete,api" - }, - url: { - label: "Target URL", - help: "External endpoint that receives the POST" - }, - method: { - label: "HTTP Method", - help: "GET / POST / PUT / PATCH / DELETE" - }, - description: { - label: "Description" - }, - active: { - label: "Active", - help: "Inactive webhooks are skipped by the dispatcher" - }, - definition_json: { - label: "Definition", - help: "Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — full headers/auth/retry/payload config" - }, - created_at: { - label: "Created At" - }, - updated_at: { - label: "Updated At" - } - }, - _views: { - active: { - label: "Active" - }, - inactive: { - label: "Inactive" - }, - by_object: { - label: "By Object" - }, - all_webhooks: { - label: "All" - } - } - }, sys_metadata: { label: "System Metadata", pluralLabel: "System Metadata", @@ -3028,8 +2367,8 @@ export const enObjects: NonNullable = { label: "Lock State", options: { none: "none", - "no-overlay": "no-overlay", - "no-delete": "no-delete", + nooverlay: "no-overlay", + nodelete: "no-delete", full: "full" } }, diff --git a/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts index 5e1a89f8d..bcc20c6d5 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts @@ -60,18 +60,123 @@ export const esESMetadataForms: NonNullable = fields: { helpText: "Añade las columnas que almacenará este objeto" }, - "fields.name": { - helpText: "Identificador snake_case" - }, "fields.label": { helpText: "Etiqueta mostrada" }, "fields.type": { helpText: "Tipo de campo" }, + "fields.description": { + helpText: "Developer documentation for this column" + }, + "fields.required": { + helpText: "Must be set on every record" + }, + "fields.unique": { + helpText: "Disallow duplicate values" + }, + "fields.indexed": { + helpText: "Create a database index for faster querying" + }, + "fields.readonly": { + helpText: "Visible but never user-editable" + }, + "fields.immutable": { + helpText: "Editable on create, locked thereafter" + }, + "fields.hidden": { + helpText: "Hidden from default UI" + }, + "fields.searchable": { + helpText: "Include in full-text search" + }, + "fields.sortable": { + helpText: "Allow sorting on this column" + }, + "fields.filterable": { + helpText: "Allow filtering on this column" + }, + "fields.defaultValue": { + helpText: "Default value for new records (JSON literal)" + }, + "fields.placeholder": { + helpText: "Placeholder hint" + }, + "fields.maxLength": { + helpText: "Max characters" + }, + "fields.minLength": { + helpText: "Min characters" + }, + "fields.min": { + helpText: "Minimum value" + }, + "fields.max": { + helpText: "Maximum value" + }, + "fields.precision": { + helpText: "Total digits" + }, + "fields.scale": { + helpText: "Decimal places" + }, + "fields.options": { + helpText: "Available choices" + }, + "fields.options.icon": { + helpText: "Lucide icon name" + }, "fields.reference": { helpText: "Objeto de destino (para lookup/master_detail)" }, + "fields.referenceFilter": { + helpText: "CEL filter applied to the picker" + }, + "fields.cascadeDelete": { + helpText: "Delete children when parent is deleted" + }, + "fields.multiple": { + helpText: "Allow selecting multiple records" + }, + "fields.formula": { + helpText: "CEL formula expression" + }, + "fields.returnType": { + helpText: "Result type for formulas" + }, + "fields.summaryType": { + helpText: "Aggregation" + }, + "fields.summaryField": { + helpText: "Field on child object to aggregate" + }, + "fields.displayFormat": { + helpText: "e.g. \"INV-{0000}\"" + }, + "fields.startingNumber": { + helpText: "Starting sequence value" + }, + "fields.language": { + helpText: "Editor language (e.g. sql, javascript)" + }, + "fields.validation": { + helpText: "CEL predicate — must evaluate true" + }, + "fields.errorMessage": { + helpText: "Shown when validation fails" + }, + "fields.audit": { + helpText: "Audit changes to this field" + }, + "fields.trackHistory": { + helpText: "Keep change history" + }, + "fields.pii": { + helpText: "Personally identifiable information" + }, + "fields.encrypted": { + helpText: "Encrypt at rest" + }, capabilities: { helpText: "Activa/desactiva funciones del sistema" }, @@ -742,58 +847,15 @@ export const esESMetadataForms: NonNullable = } } }, - workflow: { - label: "Flujo de trabajo", - sections: { - basics: { - label: "Aspectos básicos", - description: "Identidad y objeto/evento que lo dispara." - }, - actions: { - label: "Acciones", - description: "Qué hace este flujo de trabajo cuando se dispara." - }, - advanced: { - label: "Avanzado", - description: "Orden y comportamiento de ejecución." - } - }, - fields: { - name: { - helpText: "Identificador único (snake_case)" - }, - objectName: { - helpText: "Qué objeto dispara este flujo de trabajo" - }, - triggerType: { - helpText: "Cuándo ejecutar: on_create, on_update, on_delete, schedule" - }, - active: { - helpText: "Activa/desactiva este flujo de trabajo" - }, - description: { - helpText: "Qué hace este flujo de trabajo" - }, - criteria: { - helpText: "Expresión CEL: ejecutar solo cuando esta condición sea true" - }, - actions: { - helpText: "Acciones que ejecutar inmediatamente (field update, email, API call, etc.)" - }, - timeTriggers: { - helpText: "Acciones programadas (p. ej., enviar recordatorio 1 día antes del plazo)" - }, - executionOrder: { - helpText: "Orden de ejecución cuando coinciden varios flujos de trabajo (menor = antes)" - } - } - }, job: { label: "Trabajo en segundo plano" }, datasource: { label: "Fuente de datos" }, + external_catalog: { + label: "External Catalog" + }, translation: { label: "Traducción" }, @@ -817,27 +879,47 @@ export const esESMetadataForms: NonNullable = label: "Asunto", description: "Línea de asunto. Admite interpolación {{var.path}}." }, - body: { - label: "Cuerpo", - description: "Cuerpo del email. Usa {{var}} para variables. El editor resalta según el tipo de body." + html_body: { + label: "HTML body", + description: "Rich HTML body. Most clients strip , so use inline styles." + }, + plain_text_body: { + label: "Plain-text body", + description: "Optional plain-text alternative. When omitted, the service strips tags from the HTML body to derive one. Providing one improves spam scoring." + }, + variables: { + label: "Variables", + description: "Declared variables. Rendered as hints in Studio and validated by sendTemplate() when required." }, - variables_and_attachments: { - label: "Variables y adjuntos", - description: "Variables de plantilla declaradas y adjuntos de archivo opcionales." + delivery_overrides: { + label: "Delivery overrides", + description: "Optional per-template overrides for From / Reply-To." + }, + status: { + label: "Status" } }, fields: { - id: { - helpText: "id de plantilla (p. ej. auth.password_reset)" + name: { + helpText: "Dotted snake_case (e.g. auth.password_reset, crm.welcome)" }, - body: { - helpText: "Contenido del body. Se renderizará como HTML, texto plano o Markdown según Body Type." + locale: { + helpText: "BCP-47 tag — e.g. en-US, zh-CN" }, variables: { helpText: "Lista de nombres de variable referenciados en subject/body" }, - attachments: { - helpText: "[{ \"name\": \"...\", \"url\": \"...\" }]" + fromOverride: { + helpText: "{ \"name\": \"Acme Sales\", \"address\": \"sales@acme.com\" }" + }, + replyTo: { + helpText: "Reply-To email address" + }, + active: { + helpText: "When unchecked, sendTemplate() returns TEMPLATE_INACTIVE." + }, + isSystem: { + helpText: "Built-in template; tenants may override but should not delete." } } }, diff --git a/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts index 616cf7aa2..c8676bddf 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts @@ -56,6 +56,9 @@ export const esESObjects: NonNullable = { } }, _views: { + me: { + label: "My Profile" + }, all_users: { label: "Todos los usuarios" }, @@ -108,10 +111,28 @@ export const esESObjects: NonNullable = { label: "Cambiar correo", successMessage: "Correo de verificación enviado; revisa la nueva dirección para confirmar." }, + resend_verification_email: { + label: "Resend Verification Email", + successMessage: "Verification email sent — check your inbox." + }, delete_my_account: { label: "Eliminar mi cuenta", confirmText: "¿Eliminar tu cuenta de forma permanente? Esta acción no se puede deshacer: se cerrarán todas tus sesiones y se eliminarán todos los datos de tu propiedad según la política de retención configurada.", successMessage: "Cuenta eliminada" + }, + enable_two_factor: { + label: "Enable Two-Factor Auth", + successMessage: "Two-factor authentication enabled. Scan the QR code or paste the otpauth URI into your authenticator app, then verify a code to complete setup." + }, + disable_two_factor: { + label: "Disable Two-Factor Auth", + confirmText: "Turn off two-factor authentication? Your account will be less secure.", + successMessage: "Two-factor authentication disabled." + }, + generate_backup_codes: { + label: "Regenerate Backup Codes", + confirmText: "Generate a new set of backup codes? Any previously generated codes will stop working.", + successMessage: "New backup codes generated — save them somewhere safe." } } }, @@ -331,6 +352,11 @@ export const esESObjects: NonNullable = { label: "Abandonar organización", confirmText: "¿Abandonar esta organización? Perderá el acceso a todos sus recursos.", successMessage: "Ha abandonado la organización" + }, + change_slug: { + label: "Change Slug", + confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?", + successMessage: "Organization slug changed" } } }, @@ -361,6 +387,11 @@ export const esESObjects: NonNullable = { } } }, + _views: { + mine: { + label: "My Memberships" + } + }, _actions: { add_member: { label: "Añadir miembro", @@ -760,6 +791,10 @@ export const esESObjects: NonNullable = { backup_codes: { label: "Códigos de respaldo", help: "Códigos de recuperación de respaldo serializados en JSON." + }, + verified: { + label: "Verified", + help: "Whether the enrollment was confirmed with a valid TOTP code (managed by better-auth)" } }, _views: { @@ -998,6 +1033,9 @@ export const esESObjects: NonNullable = { } }, _views: { + mine: { + label: "My Applications" + }, active: { label: "Activo" }, @@ -1183,473 +1221,6 @@ export const esESObjects: NonNullable = { } } }, - sys_role: { - label: "Rol", - pluralLabel: "Roles", - description: "Definiciones de rol para el control de acceso RBAC", - fields: { - label: { - label: "Nombre visible" - }, - name: { - label: "Nombre de API", - help: "Nombre técnico único del rol (p. ej. admin, editor, viewer)." - }, - description: { - label: "Descripción" - }, - permissions: { - label: "Permisos", - help: "Matriz serializada en JSON de cadenas de permisos." - }, - active: { - label: "Activo" - }, - is_default: { - label: "Rol predeterminado", - help: "Se asigna automáticamente a los nuevos usuarios." - }, - id: { - label: "ID de rol" - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - }, - _views: { - active: { - label: "Activo" - }, - default_roles: { - label: "Predeterminado" - }, - custom: { - label: "Personalizado" - }, - all_roles: { - label: "Todos" - } - }, - _actions: { - activate_role: { - label: "Activar rol", - successMessage: "Rol activado" - }, - deactivate_role: { - label: "Desactivar rol", - confirmText: "¿Desactivar este rol? Los usuarios con el rol conservan su asignación, pero el rol deja de otorgar permisos hasta que se vuelva a activar.", - successMessage: "Rol desactivado" - }, - set_default_role: { - label: "Establecer como predeterminado", - confirmText: "¿Convertir este en el rol predeterminado para los nuevos usuarios? Los usuarios existentes no se ven afectados.", - successMessage: "Rol predeterminado actualizado" - }, - clone_role: { - label: "Clonar rol", - successMessage: "Rol clonado" - } - } - }, - sys_permission_set: { - label: "Conjunto de permisos", - pluralLabel: "Conjuntos de permisos", - description: "Agrupaciones de permisos con nombre para un control de acceso detallado", - fields: { - label: { - label: "Nombre visible" - }, - name: { - label: "Nombre de API", - help: "Nombre técnico único del conjunto de permisos." - }, - description: { - label: "Descripción" - }, - object_permissions: { - label: "Permisos de objeto", - help: "Permisos CRUD a nivel de objeto serializados en JSON." - }, - field_permissions: { - label: "Permisos de campo", - help: "Permisos de lectura/escritura a nivel de campo serializados en JSON." - }, - system_permissions: { - label: "Permisos del sistema", - help: "Array serializado en JSON de nombres de capacidades del sistema (p. ej. [\"setup.access\",\"studio.access\",\"manage_users\"])" - }, - row_level_security: { - label: "Seguridad a nivel de fila", - help: "Array serializado en JSON de políticas de seguridad a nivel de fila (cláusulas USING/CHECK)" - }, - tab_permissions: { - label: "Permisos de pestañas", - help: "Mapa serializado en JSON de la visibilidad de las pestañas de la app (visible | hidden | default_on | default_off)" - }, - active: { - label: "Activo" - }, - id: { - label: "ID de conjunto de permisos" - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - }, - _views: { - active: { - label: "Activo" - }, - inactive: { - label: "Inactivo" - }, - all_permsets: { - label: "Todos" - } - }, - _actions: { - activate_permission_set: { - label: "Activar", - successMessage: "Conjunto de permisos activado" - }, - deactivate_permission_set: { - label: "Desactivar", - confirmText: "¿Desactivar este conjunto de permisos? Las asignaciones existentes se mantienen, pero dejan de otorgar acceso hasta que se vuelva a activar.", - successMessage: "Conjunto de permisos desactivado" - }, - clone_permission_set: { - label: "Clonar", - successMessage: "Conjunto de permisos clonado" - } - } - }, - sys_user_permission_set: { - label: "Conjunto de permisos de usuario", - pluralLabel: "Conjuntos de permisos de usuario", - description: "Asignación directa de un conjunto de permisos a un usuario (opcionalmente con ámbito de organización).", - fields: { - id: { - label: "ID de asignación", - help: "UUID de la asignación." - }, - user_id: { - label: "Usuario", - help: "Clave foránea a sys_user." - }, - permission_set_id: { - label: "Conjunto de permisos", - help: "Clave foránea a sys_permission_set." - }, - organization_id: { - label: "Organización", - help: "Ámbito de organización opcional. NULL = se aplica en cualquier contexto de organización." - }, - granted_by: { - label: "Concedido por", - help: "Usuario que concedió este conjunto de permisos." - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - } - }, - sys_role_permission_set: { - label: "Conjunto de permisos de rol", - pluralLabel: "Conjuntos de permisos de rol", - description: "Vincula un conjunto de permisos a un rol.", - fields: { - id: { - label: "ID de vinculación", - help: "UUID de la vinculación rol-conjunto de permisos." - }, - role_id: { - label: "Rol", - help: "Clave foránea a sys_role." - }, - permission_set_id: { - label: "Conjunto de permisos", - help: "Clave foránea a sys_permission_set." - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - } - }, - sys_record_share: { - label: "Compartición de registro", - pluralLabel: "Comparticiones de registro", - description: "Concesión de compartición por registro; amplía OWD con acceso explícito.", - fields: { - id: { - label: "ID de compartición" - }, - object_name: { - label: "Objeto", - help: "Nombre corto del objeto del registro compartido." - }, - record_id: { - label: "Registro", - help: "Clave primaria del registro compartido dentro de object_name." - }, - recipient_type: { - label: "Tipo de destinatario", - help: "Tipo de principal que posee la concesión.", - options: { - user: "Usuario", - group: "Grupo", - role: "Rol", - role_and_subordinates: "Rol y subordinados", - guest: "Invitado" - } - }, - recipient_id: { - label: "Destinatario", - help: "ID del usuario/grupo/rol que recibe acceso." - }, - access_level: { - label: "Nivel de acceso", - help: "Lo que puede hacer el destinatario: read | edit | full (transfer/share/delete).", - options: { - read: "Leer", - edit: "Editar", - full: "Total" - } - }, - source: { - label: "Origen", - help: "Motivo por el que existe esta concesión; lo utiliza el evaluador de reglas para reconciliar.", - options: { - manual: "Manual", - rule: "Regla", - team: "Equipo", - inherited: "Heredado" - } - }, - source_id: { - label: "ID de origen", - help: "Nombre de la regla / ID del equipo cuando source != manual." - }, - granted_by: { - label: "Concedido por", - help: "Usuario que creó la concesión (solo manual)." - }, - reason: { - label: "Motivo", - help: "Explicación opcional en texto libre que se muestra al destinatario." - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - }, - _views: { - granted_to_me: { - label: "Concedidos a mí" - }, - granted_by_me: { - label: "Concedidos por mí" - }, - by_object: { - label: "Por objeto" - }, - manual_grants: { - label: "Concesiones manuales" - }, - rule_grants: { - label: "Concesiones por regla" - }, - all_shares: { - label: "Todas" - } - } - }, - sys_sharing_rule: { - label: "Regla de compartición", - pluralLabel: "Reglas de compartición", - description: "Regla de compartición declarativa que materializa automáticamente concesiones de sys_record_share. Se define mediante defineSharingRule() en código o con el generador de criterios de Studio.", - fields: { - id: { - label: "ID de regla" - }, - organization_id: { - label: "Organización", - help: "Tenant que posee esta regla; null = global." - }, - name: { - label: "Nombre", - help: "Nombre de regla snake_case único." - }, - label: { - label: "Nombre visible" - }, - description: { - label: "Descripción" - }, - object_name: { - label: "Objeto", - help: "Nombre corto del objeto (p. ej. opportunity, account)." - }, - criteria_json: { - label: "Criterios (JSON de FilterCondition)", - help: "FilterCondition JSON comparado con los registros de object_name. Vacío = coincide con todos." - }, - recipient_type: { - label: "Tipo de destinatario", - help: "Tipo de principal que recibe acceso; se expande a concesiones de usuario durante la evaluación. `department` recorre el árbol parent_department_id; `team` es plano (better-auth).", - options: { - user: "Usuario", - team: "Equipo", - department: "Departamento", - role: "Rol", - queue: "Cola" - } - }, - recipient_id: { - label: "Destinatario", - help: "ID de departamento / ID de equipo / nombre del rol / nombre de la cola / ID del usuario según recipient_type." - }, - access_level: { - label: "Nivel de acceso", - options: { - read: "Leer", - edit: "Editar", - full: "Total" - } - }, - active: { - label: "Activo", - help: "Solo las reglas activas participan en la evaluación del ciclo de vida." - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - }, - _views: { - active: { - label: "Activo" - }, - inactive: { - label: "Inactivo" - }, - by_object: { - label: "Por objeto" - }, - all_rules: { - label: "Todas" - } - } - }, - sys_share_link: { - label: "Enlace de uso compartido", - pluralLabel: "Enlaces de uso compartido", - description: "Token de capacidad opaco que concede acceso a un único registro. Uso compartido mediante enlace público al estilo de Notion/Figma.", - fields: { - id: { - label: "ID del enlace" - }, - token: { - label: "Token", - help: "Token aleatorio opaco seguro para URL (≥ 22 caracteres). El único secreto de esta fila." - }, - object_name: { - label: "Objeto", - help: "Nombre corto del objeto del registro compartido (p. ej. ai_conversation, contracts_contract)" - }, - record_id: { - label: "Registro", - help: "Clave principal del registro compartido dentro de object_name" - }, - permission: { - label: "Permiso", - help: "Lo que el titular del enlace puede hacer con el registro", - options: { - view: "Ver", - comment: "Comentar", - edit: "Editar" - } - }, - audience: { - label: "Audiencia", - help: "Capa de control aplicada por encima de la verificación del token", - options: { - public: "Público (indexable)", - link_only: "Cualquier persona con el enlace", - signed_in: "Usuarios con sesión iniciada", - email: "Correos específicos" - } - }, - expires_at: { - label: "Caduca el", - help: "Cuando se establece, resolveToken devuelve null después de esta marca de tiempo" - }, - email_allowlist: { - label: "Lista de correos permitidos", - help: "Direcciones en minúsculas que se comprueban cuando audience=email" - }, - password_hash: { - label: "Hash de contraseña", - help: "Hash Argon2/bcrypt. Cuando se establece, la interfaz solicita una contraseña antes de mostrar el contenido." - }, - redact_fields: { - label: "Campos ocultos por enlace", - help: "Campos adicionales que se eliminan de la respuesta, además del conjunto predeterminado del objeto" - }, - label: { - label: "Etiqueta", - help: "Texto libre que se muestra en el cuadro de diálogo de uso compartido (p. ej. \"ACME Q3 contract\")" - }, - revoked_at: { - label: "Revocado el", - help: "Cuando se establece, el enlace queda deshabilitado permanentemente" - }, - created_by: { - label: "Creado por", - help: "Emisor del enlace" - }, - created_at: { - label: "Creado el" - }, - last_used_at: { - label: "Último uso el", - help: "Lo registra resolveToken; el panel lo usa para resaltar los enlaces activos" - }, - use_count: { - label: "Número de usos", - help: "Lo incrementa resolveToken en cada resolución correcta" - } - }, - _views: { - active_links: { - label: "Activos" - }, - by_me: { - label: "Creados por mí" - }, - revoked: { - label: "Revocados" - }, - all_links: { - label: "Todos" - } - } - }, sys_audit_log: { label: "Registro de auditoría", pluralLabel: "Registros de auditoría", @@ -1979,28 +1550,26 @@ export const esESObjects: NonNullable = { id: { label: "ID de notificación" }, - recipient_id: { - label: "Destinatario", - help: "Usuario al que se entrega la notificación." + topic: { + label: "Topic", + help: "Notification topic, e.g. task.assigned, collab.mention" }, - type: { - label: "Tipo", - help: "Categoría de notificación; controla el icono y la prioridad de ordenación.", + payload: { + label: "Payload", + help: "Template inputs carried to channels (title/body/url/actor/source/…)" + }, + severity: { + label: "Severity", + help: "Severity hint for rendering / filtering", options: { - mention: "Mención", - assignment: "Asignación", - comment_reply: "Respuesta a comentario", - lead_converted: "Prospecto convertido", - task_due: "Tarea vencida", - system: "Sistema" + info: "info", + warning: "warning", + critical: "critical" } }, - title: { - label: "Título" - }, - body: { - label: "Contenido", - help: "Texto secundario opcional (resumen de una línea)." + dedup_key: { + label: "Dedup Key", + help: "Idempotency key within a topic window; a repeat emit is a no-op" }, source_object: { label: "Objeto de origen", @@ -2010,50 +1579,20 @@ export const esESObjects: NonNullable = { label: "Registro de origen", help: "ID del registro dentro de source_object." }, - url: { - label: "Enlace profundo", - help: "URL opcional a la que navegar al hacer clic." - }, actor_id: { label: "Actor", help: "Usuario que provocó la notificación (quien menciona, quien asigna)." }, - actor_name: { - label: "Nombre del actor" - }, - is_read: { - label: "Leído", - help: "Verdadero una vez que el destinatario la reconoce." - }, - read_at: { - label: "Leído el" - }, created_at: { label: "Creado el" - }, - updated_at: { - label: "Actualizado el" } }, _views: { - unread: { - label: "No leídas" - }, - mine: { - label: "Mías" - }, - all_notifications: { - label: "Todas" - } - }, - _actions: { - mark_read: { - label: "Marcar como leído", - successMessage: "Notificación marcada como leída" + recent: { + label: "Recent" }, - mark_unread: { - label: "Marcar como no leído", - successMessage: "Notificación marcada como no leída" + by_topic: { + label: "By Topic" } } }, @@ -2331,142 +1870,6 @@ export const esESObjects: NonNullable = { } } }, - sys_approval_request: { - label: "Solicitud de aprobación", - pluralLabel: "Solicitudes de aprobación", - description: "Instancia activa de aprobación registrada por envío", - fields: { - id: { - label: "ID de solicitud" - }, - organization_id: { - label: "Organización", - help: "Tenant que posee esta solicitud de aprobación (propagado desde el contexto del solicitante)." - }, - process_name: { - label: "Origen", - help: "Origen de la solicitud — `flow:` para aprobaciones por nodo" - }, - object_name: { - label: "Objeto" - }, - record_id: { - label: "ID de registro" - }, - submitter_id: { - label: "Solicitante" - }, - submitter_comment: { - label: "Comentario del solicitante" - }, - status: { - label: "Estado", - help: "Estado del ciclo de vida de la solicitud.", - options: { - pending: "Pendiente", - approved: "Aprobada", - rejected: "Rechazada", - recalled: "Retirada" - } - }, - current_step: { - label: "Paso actual", - help: "Nombre técnico del paso pendiente de aprobación." - }, - current_step_index: { - label: "Índice del paso actual" - }, - pending_approvers: { - label: "Aprobadores pendientes", - help: "ID de usuario separados por comas que pueden actuar en el paso actual." - }, - payload_json: { - label: "Instantánea", - help: "Instantánea del registro en el momento del envío." - }, - process_hash: { - label: "Hash del proceso", - help: "sha256 del cuerpo del proceso de aprobación en el momento del envío (fijación de ejecución de ADR-0009). Se resuelve a través de sys_metadata_history para que las actualizaciones del proceso no afecten a las solicitudes en curso." - }, - completed_at: { - label: "Completado el" - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - }, - _views: { - my_pending: { - label: "Mis pendientes" - }, - submitted_by_me: { - label: "Enviadas por mí" - }, - completed: { - label: "Completadas" - }, - all_requests: { - label: "Todas" - } - } - }, - sys_approval_action: { - label: "Acción de aprobación", - pluralLabel: "Acciones de aprobación", - description: "Registro de auditoría append-only para acciones de aprobación", - fields: { - id: { - label: "ID de acción" - }, - organization_id: { - label: "Organización", - help: "Tenant que posee esta acción (refleja la solicitud principal)." - }, - request_id: { - label: "Solicitud" - }, - step_name: { - label: "Paso", - help: "Nombre técnico del paso en el momento de la acción." - }, - step_index: { - label: "Índice del paso" - }, - action: { - label: "Acción", - options: { - submit: "Enviar", - approve: "Aprobar", - reject: "Rechazar", - recall: "Retirar", - escalate: "Escalar" - } - }, - actor_id: { - label: "Actor" - }, - comment: { - label: "Comentario" - }, - created_at: { - label: "Creado el" - } - }, - _views: { - recent: { - label: "Recientes" - }, - by_actor: { - label: "Por actor" - }, - all_actions: { - label: "Todas" - } - } - }, sys_job: { label: "Tarea en segundo plano", pluralLabel: "Tareas en segundo plano", @@ -2660,70 +2063,6 @@ export const esESObjects: NonNullable = { } } }, - sys_webhook: { - label: "Webhook", - pluralLabel: "Webhooks", - description: "Suscripción saliente de Webhook HTTP. Se crea mediante defineWebhook() en código o con el editor de Studio; la ejecuta el plugin del conector HTTP.", - fields: { - id: { - label: "ID de webhook" - }, - name: { - label: "Nombre", - help: "Nombre snake_case único; se usa en los registros y en la auditoría." - }, - label: { - label: "Nombre visible" - }, - object_name: { - label: "Objeto", - help: "Nombre corto del objeto cuyos eventos activan este Webhook (vacío = activación manual / API)." - }, - triggers: { - label: "Desencadenantes", - help: "Lista de eventos separada por comas: create,update,delete,undelete,api." - }, - url: { - label: "URL de destino", - help: "Endpoint externo que recibe el POST." - }, - method: { - label: "Método HTTP", - help: "GET / POST / PUT / PATCH / DELETE" - }, - description: { - label: "Descripción" - }, - active: { - label: "Activo", - help: "Los Webhooks inactivos se omiten en el despachador." - }, - definition_json: { - label: "Definición", - help: "JSON serializado de Webhook (consulte @objectstack/spec/automation/webhook): configuración completa de cabeceras/auth/reintentos/payload." - }, - created_at: { - label: "Creado el" - }, - updated_at: { - label: "Actualizado el" - } - }, - _views: { - active: { - label: "Activo" - }, - inactive: { - label: "Inactivo" - }, - by_object: { - label: "Por objeto" - }, - all_webhooks: { - label: "Todos" - } - } - }, sys_metadata: { label: "Metadatos del sistema", pluralLabel: "Metadatos del sistema", @@ -3028,8 +2367,8 @@ export const esESObjects: NonNullable = { label: "Estado de bloqueo", options: { none: "Ninguno", - "no-overlay": "Sin superposición", - "no-delete": "Sin eliminación", + nooverlay: "no-overlay", + nodelete: "no-delete", full: "Bloqueo total" } }, diff --git a/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts index 3a4c3aa03..c512be5c1 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts @@ -60,18 +60,123 @@ export const jaJPMetadataForms: NonNullable = fields: { helpText: "このオブジェクトが保存する列を追加" }, - "fields.name": { - helpText: "snake_case 識別子" - }, "fields.label": { helpText: "表示ラベル" }, "fields.type": { helpText: "フィールド型" }, + "fields.description": { + helpText: "Developer documentation for this column" + }, + "fields.required": { + helpText: "Must be set on every record" + }, + "fields.unique": { + helpText: "Disallow duplicate values" + }, + "fields.indexed": { + helpText: "Create a database index for faster querying" + }, + "fields.readonly": { + helpText: "Visible but never user-editable" + }, + "fields.immutable": { + helpText: "Editable on create, locked thereafter" + }, + "fields.hidden": { + helpText: "Hidden from default UI" + }, + "fields.searchable": { + helpText: "Include in full-text search" + }, + "fields.sortable": { + helpText: "Allow sorting on this column" + }, + "fields.filterable": { + helpText: "Allow filtering on this column" + }, + "fields.defaultValue": { + helpText: "Default value for new records (JSON literal)" + }, + "fields.placeholder": { + helpText: "Placeholder hint" + }, + "fields.maxLength": { + helpText: "Max characters" + }, + "fields.minLength": { + helpText: "Min characters" + }, + "fields.min": { + helpText: "Minimum value" + }, + "fields.max": { + helpText: "Maximum value" + }, + "fields.precision": { + helpText: "Total digits" + }, + "fields.scale": { + helpText: "Decimal places" + }, + "fields.options": { + helpText: "Available choices" + }, + "fields.options.icon": { + helpText: "Lucide icon name" + }, "fields.reference": { helpText: "対象オブジェクト(lookup/master_detail 用)" }, + "fields.referenceFilter": { + helpText: "CEL filter applied to the picker" + }, + "fields.cascadeDelete": { + helpText: "Delete children when parent is deleted" + }, + "fields.multiple": { + helpText: "Allow selecting multiple records" + }, + "fields.formula": { + helpText: "CEL formula expression" + }, + "fields.returnType": { + helpText: "Result type for formulas" + }, + "fields.summaryType": { + helpText: "Aggregation" + }, + "fields.summaryField": { + helpText: "Field on child object to aggregate" + }, + "fields.displayFormat": { + helpText: "e.g. \"INV-{0000}\"" + }, + "fields.startingNumber": { + helpText: "Starting sequence value" + }, + "fields.language": { + helpText: "Editor language (e.g. sql, javascript)" + }, + "fields.validation": { + helpText: "CEL predicate — must evaluate true" + }, + "fields.errorMessage": { + helpText: "Shown when validation fails" + }, + "fields.audit": { + helpText: "Audit changes to this field" + }, + "fields.trackHistory": { + helpText: "Keep change history" + }, + "fields.pii": { + helpText: "Personally identifiable information" + }, + "fields.encrypted": { + helpText: "Encrypt at rest" + }, capabilities: { helpText: "システム機能の有効/無効" }, @@ -742,58 +847,15 @@ export const jaJPMetadataForms: NonNullable = } } }, - workflow: { - label: "ワークフロー", - sections: { - basics: { - label: "基本", - description: "ID と発火元のオブジェクト/イベント。" - }, - actions: { - label: "アクション", - description: "発火時にこのワークフローが行う処理。" - }, - advanced: { - label: "詳細", - description: "順序と実行動作。" - } - }, - fields: { - name: { - helpText: "一意識別子(snake_case)" - }, - objectName: { - helpText: "このワークフローを発火するオブジェクト" - }, - triggerType: { - helpText: "実行タイミング: on_create, on_update, on_delete, schedule" - }, - active: { - helpText: "このワークフローの有効/無効" - }, - description: { - helpText: "このワークフローの処理内容" - }, - criteria: { - helpText: "CEL 式: この条件が true の場合のみ実行" - }, - actions: { - helpText: "即時実行するアクション(field update, email, API call など)" - }, - timeTriggers: { - helpText: "スケジュールアクション(例: 期限 1 日前にリマインダー送信)" - }, - executionOrder: { - helpText: "複数ワークフロー一致時の実行順(小さいほど早い)" - } - } - }, job: { label: "バックグラウンドジョブ" }, datasource: { label: "データソース" }, + external_catalog: { + label: "External Catalog" + }, translation: { label: "翻訳" }, @@ -817,27 +879,47 @@ export const jaJPMetadataForms: NonNullable = label: "件名", description: "件名行。{{var.path}} 補間をサポート。" }, - body: { - label: "本文", - description: "メール本文。変数には {{var}} を使用。エディターは body 型に基づきハイライト。" + html_body: { + label: "HTML body", + description: "Rich HTML body. Most clients strip , so use inline styles." + }, + plain_text_body: { + label: "Plain-text body", + description: "Optional plain-text alternative. When omitted, the service strips tags from the HTML body to derive one. Providing one improves spam scoring." + }, + variables: { + label: "Variables", + description: "Declared variables. Rendered as hints in Studio and validated by sendTemplate() when required." }, - variables_and_attachments: { - label: "変数と添付ファイル", - description: "宣言済みテンプレート変数と任意の添付ファイル。" + delivery_overrides: { + label: "Delivery overrides", + description: "Optional per-template overrides for From / Reply-To." + }, + status: { + label: "Status" } }, fields: { - id: { - helpText: "テンプレート id(例: auth.password_reset)" + name: { + helpText: "Dotted snake_case (e.g. auth.password_reset, crm.welcome)" }, - body: { - helpText: "本文コンテンツ。Body Type に基づき HTML、プレーンテキスト、Markdown としてレンダリング。" + locale: { + helpText: "BCP-47 tag — e.g. en-US, zh-CN" }, variables: { helpText: "subject/body で参照する変数名リスト" }, - attachments: { - helpText: "[{ \"name\": \"...\", \"url\": \"...\" }]" + fromOverride: { + helpText: "{ \"name\": \"Acme Sales\", \"address\": \"sales@acme.com\" }" + }, + replyTo: { + helpText: "Reply-To email address" + }, + active: { + helpText: "When unchecked, sendTemplate() returns TEMPLATE_INACTIVE." + }, + isSystem: { + helpText: "Built-in template; tenants may override but should not delete." } } }, diff --git a/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts index 2ad89f789..9714cab86 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts @@ -56,6 +56,9 @@ export const jaJPObjects: NonNullable = { } }, _views: { + me: { + label: "My Profile" + }, all_users: { label: "すべてのユーザー" }, @@ -108,10 +111,28 @@ export const jaJPObjects: NonNullable = { label: "メールアドレス変更", successMessage: "確認メールを送信しました。新しいメールアドレスで確認してください。" }, + resend_verification_email: { + label: "Resend Verification Email", + successMessage: "Verification email sent — check your inbox." + }, delete_my_account: { label: "アカウント削除", confirmText: "アカウントを完全に削除しますか?この操作は元に戻せません。すべてのセッションが終了され、設定された保持ポリシーに従って所有するすべてのデータが削除されます。", successMessage: "アカウントを削除しました" + }, + enable_two_factor: { + label: "Enable Two-Factor Auth", + successMessage: "Two-factor authentication enabled. Scan the QR code or paste the otpauth URI into your authenticator app, then verify a code to complete setup." + }, + disable_two_factor: { + label: "Disable Two-Factor Auth", + confirmText: "Turn off two-factor authentication? Your account will be less secure.", + successMessage: "Two-factor authentication disabled." + }, + generate_backup_codes: { + label: "Regenerate Backup Codes", + confirmText: "Generate a new set of backup codes? Any previously generated codes will stop working.", + successMessage: "New backup codes generated — save them somewhere safe." } } }, @@ -331,6 +352,11 @@ export const jaJPObjects: NonNullable = { label: "組織から退出", confirmText: "この組織から退出しますか?すべてのリソースへのアクセスを失います。", successMessage: "組織から退出しました" + }, + change_slug: { + label: "Change Slug", + confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?", + successMessage: "Organization slug changed" } } }, @@ -361,6 +387,11 @@ export const jaJPObjects: NonNullable = { } } }, + _views: { + mine: { + label: "My Memberships" + } + }, _actions: { add_member: { label: "メンバーを追加", @@ -760,6 +791,10 @@ export const jaJPObjects: NonNullable = { backup_codes: { label: "バックアップコード", help: "JSON シリアライズされたバックアップ回復コード" + }, + verified: { + label: "Verified", + help: "Whether the enrollment was confirmed with a valid TOTP code (managed by better-auth)" } }, _views: { @@ -998,6 +1033,9 @@ export const jaJPObjects: NonNullable = { } }, _views: { + mine: { + label: "My Applications" + }, active: { label: "有効" }, @@ -1183,473 +1221,6 @@ export const jaJPObjects: NonNullable = { } } }, - sys_role: { - label: "ロール", - pluralLabel: "ロール", - description: "RBAC アクセス制御のためのロール定義", - fields: { - label: { - label: "表示名" - }, - name: { - label: "API 名", - help: "ロールの一意の機械名(例: admin、editor、viewer)" - }, - description: { - label: "説明" - }, - permissions: { - label: "権限", - help: "権限文字列の JSON シリアライズ配列" - }, - active: { - label: "有効" - }, - is_default: { - label: "デフォルトロール", - help: "新規ユーザーに自動的に割り当てられます" - }, - id: { - label: "ロール ID" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - }, - _views: { - active: { - label: "有効" - }, - default_roles: { - label: "デフォルト" - }, - custom: { - label: "カスタム" - }, - all_roles: { - label: "すべて" - } - }, - _actions: { - activate_role: { - label: "ロールを有効化", - successMessage: "ロールが有効化されました" - }, - deactivate_role: { - label: "ロールを無効化", - confirmText: "このロールを無効化しますか?このロールを持つユーザーの割り当ては維持されますが、再度有効化するまで権限の付与は停止されます。", - successMessage: "ロールが無効化されました" - }, - set_default_role: { - label: "デフォルトに設定", - confirmText: "このロールを新規ユーザーのデフォルトロールにしますか?既存のユーザーには影響しません。", - successMessage: "デフォルトロールを更新しました" - }, - clone_role: { - label: "ロールを複製", - successMessage: "ロールを複製しました" - } - } - }, - sys_permission_set: { - label: "権限セット", - pluralLabel: "権限セット", - description: "細かいアクセス制御のための権限グループ", - fields: { - label: { - label: "表示名" - }, - name: { - label: "API 名", - help: "権限セットの一意の機械名" - }, - description: { - label: "説明" - }, - object_permissions: { - label: "オブジェクト権限", - help: "JSON シリアライズされたオブジェクトレベルの CRUD 権限" - }, - field_permissions: { - label: "フィールド権限", - help: "JSON シリアライズされたフィールドレベルの読み取り/書き込み権限" - }, - system_permissions: { - label: "システム権限", - help: "システムケーパビリティ名のJSONシリアライズ配列(例: [\"setup.access\",\"studio.access\",\"manage_users\"])" - }, - row_level_security: { - label: "行レベルセキュリティ", - help: "行レベルセキュリティポリシーのJSONシリアライズ配列(USING/CHECK 句)" - }, - tab_permissions: { - label: "タブ権限", - help: "アプリのタブ表示のJSONシリアライズマップ(visible | hidden | default_on | default_off)" - }, - active: { - label: "有効" - }, - id: { - label: "権限セット ID" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - }, - _views: { - active: { - label: "有効" - }, - inactive: { - label: "無効" - }, - all_permsets: { - label: "すべて" - } - }, - _actions: { - activate_permission_set: { - label: "有効化", - successMessage: "権限セットが有効化されました" - }, - deactivate_permission_set: { - label: "無効化", - confirmText: "この権限セットを無効化しますか?既存の割り当ては維持されますが、再度有効化するまでアクセスの付与は停止されます。", - successMessage: "権限セットが無効化されました" - }, - clone_permission_set: { - label: "複製", - successMessage: "権限セットを複製しました" - } - } - }, - sys_user_permission_set: { - label: "ユーザー権限セット", - pluralLabel: "ユーザー権限セット", - description: "ユーザーへの権限セットの直接割り当て(組織スコープ可能)。", - fields: { - id: { - label: "割り当て ID", - help: "割り当ての UUID。" - }, - user_id: { - label: "ユーザー", - help: "sys_user への外部キー。" - }, - permission_set_id: { - label: "権限セット", - help: "sys_permission_set への外部キー。" - }, - organization_id: { - label: "組織", - help: "オプションの組織スコープ。NULL = すべての組織コンテキストで適用。" - }, - granted_by: { - label: "付与者", - help: "この権限セットを付与したユーザー。" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - } - }, - sys_role_permission_set: { - label: "ロール権限セット", - pluralLabel: "ロール権限セット", - description: "権限セットをロールにバインドします。", - fields: { - id: { - label: "バインド ID", - help: "ロール権限セットバインドの UUID。" - }, - role_id: { - label: "ロール", - help: "sys_role への外部キー。" - }, - permission_set_id: { - label: "権限セット", - help: "sys_permission_set への外部キー。" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - } - }, - sys_record_share: { - label: "レコード共有", - pluralLabel: "レコード共有", - description: "レコードごとの共有付与 — OWD に明示的なアクセスを追加", - fields: { - id: { - label: "共有 ID" - }, - object_name: { - label: "オブジェクト", - help: "共有レコードの短いオブジェクト名" - }, - record_id: { - label: "レコード", - help: "object_name 内の共有レコードの主キー" - }, - recipient_type: { - label: "受信者タイプ", - help: "付与を保持するプリンシパルの種別", - options: { - user: "ユーザー", - group: "グループ", - role: "ロール", - role_and_subordinates: "ロールと下位階層", - guest: "ゲスト" - } - }, - recipient_id: { - label: "受信者", - help: "アクセスを受け取るユーザー/グループ/ロールの ID" - }, - access_level: { - label: "アクセスレベル", - help: "受信者に許可される操作 — read | edit | full(転送/共有/削除)", - options: { - read: "閲覧", - edit: "編集", - full: "フルアクセス" - } - }, - source: { - label: "ソース", - help: "この付与が存在する理由 — ルール評価者が調整に使用", - options: { - manual: "手動", - rule: "ルール", - team: "チーム", - inherited: "継承" - } - }, - source_id: { - label: "ソース ID", - help: "source != manual の場合のルール名 / チーム ID" - }, - granted_by: { - label: "付与者", - help: "付与を作成したユーザー(手動のみ)" - }, - reason: { - label: "理由", - help: "受信者に表示されるオプションの自由記述説明" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - }, - _views: { - granted_to_me: { - label: "自分への付与" - }, - granted_by_me: { - label: "自分による付与" - }, - by_object: { - label: "オブジェクト別" - }, - manual_grants: { - label: "手動付与" - }, - rule_grants: { - label: "ルール付与" - }, - all_shares: { - label: "すべて" - } - } - }, - sys_sharing_rule: { - label: "共有ルール", - pluralLabel: "共有ルール", - description: "sys_record_share 付与を自動マテリアライズする宣言的共有ルール。コードの defineSharingRule() または Studio の条件ビルダーで作成します。", - fields: { - id: { - label: "ルール ID" - }, - organization_id: { - label: "組織", - help: "このルールを所有するテナント。null = グローバル" - }, - name: { - label: "名前", - help: "一意の snake_case ルール名" - }, - label: { - label: "表示名" - }, - description: { - label: "説明" - }, - object_name: { - label: "オブジェクト", - help: "短いオブジェクト名(例: opportunity、account)" - }, - criteria_json: { - label: "条件(FilterCondition JSON)", - help: "object_name のレコードに対してマッチする JSON FilterCondition。空 = すべてにマッチ。" - }, - recipient_type: { - label: "受信者タイプ", - help: "アクセスを受け取るプリンシパルの種別 — 評価時にユーザー付与に展開されます。`department` は parent_department_id ツリーをたどります。`team` はフラット(better-auth)。", - options: { - user: "ユーザー", - team: "チーム", - department: "部門", - role: "ロール", - queue: "キュー" - } - }, - recipient_id: { - label: "受信者", - help: "recipient_type に応じた部門 ID / チーム ID / ロール名 / キュー名 / ユーザー ID" - }, - access_level: { - label: "アクセスレベル", - options: { - read: "閲覧", - edit: "編集", - full: "フルアクセス" - } - }, - active: { - label: "有効", - help: "有効なルールのみがライフサイクル評価に参加します" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - }, - _views: { - active: { - label: "有効" - }, - inactive: { - label: "無効" - }, - by_object: { - label: "オブジェクト別" - }, - all_rules: { - label: "すべて" - } - } - }, - sys_share_link: { - label: "共有リンク", - pluralLabel: "共有リンク", - description: "単一レコードへのアクセスを許可する不透明なケーパビリティトークン。Notion / Figma スタイルの公開リンク共有。", - fields: { - id: { - label: "リンク ID" - }, - token: { - label: "トークン", - help: "URL セーフな不透明ランダムトークン(22 文字以上)。この行で唯一の機密情報です。" - }, - object_name: { - label: "オブジェクト", - help: "共有対象レコードのオブジェクト短縮名(例: ai_conversation、contracts_contract)" - }, - record_id: { - label: "レコード", - help: "object_name 内における共有対象レコードの主キー" - }, - permission: { - label: "権限", - help: "リンク保持者がレコードに対して実行できる操作", - options: { - view: "閲覧", - comment: "コメント", - edit: "編集" - } - }, - audience: { - label: "対象者", - help: "トークン検証の上に適用されるアクセス制御レイヤー", - options: { - public: "公開(インデックス可能)", - link_only: "リンクを知っている全員", - signed_in: "サインイン済みユーザー", - email: "特定のメールアドレス" - } - }, - expires_at: { - label: "有効期限", - help: "設定すると、このタイムスタンプ以降は resolveToken が null を返します" - }, - email_allowlist: { - label: "メール許可リスト", - help: "audience=email のときに照合される小文字のメールアドレス" - }, - password_hash: { - label: "パスワードハッシュ", - help: "Argon2/bcrypt ハッシュ。設定すると、表示前に UI がパスワードの入力を求めます。" - }, - redact_fields: { - label: "リンク単位のマスキング", - help: "オブジェクト既定のマスキング集合に加えて、レスポンスから除外する追加フィールド" - }, - label: { - label: "ラベル", - help: "共有ダイアログに表示される自由記述テキスト(例: \"ACME Q3 contract\")" - }, - revoked_at: { - label: "失効日時", - help: "設定すると、リンクは恒久的に無効化されます" - }, - created_by: { - label: "作成者", - help: "リンクの発行者" - }, - created_at: { - label: "作成日時" - }, - last_used_at: { - label: "最終使用日時", - help: "resolveToken によって記録されます。ダッシュボードでアクティブなリンクを強調表示するために使用されます" - }, - use_count: { - label: "使用回数", - help: "解決が成功するたびに resolveToken によって加算されます" - } - }, - _views: { - active_links: { - label: "アクティブ" - }, - by_me: { - label: "自分が作成" - }, - revoked: { - label: "失効済み" - }, - all_links: { - label: "すべて" - } - } - }, sys_audit_log: { label: "監査ログ", pluralLabel: "監査ログ", @@ -1979,28 +1550,26 @@ export const jaJPObjects: NonNullable = { id: { label: "通知 ID" }, - recipient_id: { - label: "受信者", - help: "通知が配信されるユーザー" + topic: { + label: "Topic", + help: "Notification topic, e.g. task.assigned, collab.mention" }, - type: { - label: "タイプ", - help: "通知カテゴリー — アイコンと並び替え優先度を決定します", + payload: { + label: "Payload", + help: "Template inputs carried to channels (title/body/url/actor/source/…)" + }, + severity: { + label: "Severity", + help: "Severity hint for rendering / filtering", options: { - mention: "メンション", - assignment: "割り当て", - comment_reply: "コメント返信", - lead_converted: "リード変換", - task_due: "タスク期限", - system: "システム" + info: "info", + warning: "warning", + critical: "critical" } }, - title: { - label: "タイトル" - }, - body: { - label: "本文", - help: "オプションの補足テキスト(1行サマリー)" + dedup_key: { + label: "Dedup Key", + help: "Idempotency key within a topic window; a repeat emit is a no-op" }, source_object: { label: "ソースオブジェクト", @@ -2010,50 +1579,20 @@ export const jaJPObjects: NonNullable = { label: "ソースレコード", help: "source_object 内のレコード ID" }, - url: { - label: "ディープリンク", - help: "クリック時に遷移するオプションの URL" - }, actor_id: { label: "操作者", help: "通知を引き起こしたユーザー(メンション者、担当者)" }, - actor_name: { - label: "操作者名" - }, - is_read: { - label: "既読", - help: "受信者が確認すると true になります" - }, - read_at: { - label: "既読日時" - }, created_at: { label: "作成日時" - }, - updated_at: { - label: "更新日時" } }, _views: { - unread: { - label: "未読" - }, - mine: { - label: "自分の通知" - }, - all_notifications: { - label: "すべて" - } - }, - _actions: { - mark_read: { - label: "既読にする", - successMessage: "通知を既読にしました" + recent: { + label: "Recent" }, - mark_unread: { - label: "未読にする", - successMessage: "通知を未読にしました" + by_topic: { + label: "By Topic" } } }, @@ -2331,142 +1870,6 @@ export const jaJPObjects: NonNullable = { } } }, - sys_approval_request: { - label: "承認リクエスト", - pluralLabel: "承認リクエスト", - description: "送信ごとに追跡されるライブ承認インスタンス", - fields: { - id: { - label: "リクエスト ID" - }, - organization_id: { - label: "組織", - help: "この承認リクエストを所有するテナント(送信者コンテキストから伝播)" - }, - process_name: { - label: "ソース", - help: "リクエストの発生元 — ノード駆動の承認では `flow:`" - }, - object_name: { - label: "オブジェクト" - }, - record_id: { - label: "レコード ID" - }, - submitter_id: { - label: "送信者" - }, - submitter_comment: { - label: "送信者コメント" - }, - status: { - label: "ステータス", - help: "リクエストのライフサイクル状態", - options: { - pending: "保留中", - approved: "承認済み", - rejected: "却下済み", - recalled: "取り消し済み" - } - }, - current_step: { - label: "現在のステップ", - help: "承認待ちのステップの機械名" - }, - current_step_index: { - label: "現在のステップ番号" - }, - pending_approvers: { - label: "承認待ち承認者", - help: "現在のステップを処理できるユーザー ID のカンマ区切りリスト" - }, - payload_json: { - label: "スナップショット", - help: "送信時のレコードスナップショット" - }, - process_hash: { - label: "プロセスハッシュ", - help: "送信時点の承認プロセス本体の sha256(ADR-0009 の実行ピン留め)。sys_metadata_history を介して解決されるため、プロセスのアップグレードは処理中のリクエストに影響しません。" - }, - completed_at: { - label: "完了日時" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - }, - _views: { - my_pending: { - label: "自分の保留中" - }, - submitted_by_me: { - label: "自分が送信" - }, - completed: { - label: "完了済み" - }, - all_requests: { - label: "すべて" - } - } - }, - sys_approval_action: { - label: "承認アクション", - pluralLabel: "承認アクション", - description: "承認アクションの追記専用監査証跡", - fields: { - id: { - label: "アクション ID" - }, - organization_id: { - label: "組織", - help: "このアクションを所有するテナント(親リクエストと同じ)" - }, - request_id: { - label: "リクエスト" - }, - step_name: { - label: "ステップ", - help: "アクション時点のステップの機械名" - }, - step_index: { - label: "ステップ番号" - }, - action: { - label: "アクション", - options: { - submit: "申請", - approve: "承認", - reject: "却下", - recall: "取消", - escalate: "エスカレーション" - } - }, - actor_id: { - label: "操作者" - }, - comment: { - label: "コメント" - }, - created_at: { - label: "作成日時" - } - }, - _views: { - recent: { - label: "最近" - }, - by_actor: { - label: "操作者別" - }, - all_actions: { - label: "すべて" - } - } - }, sys_job: { label: "ジョブ", pluralLabel: "ジョブ", @@ -2660,70 +2063,6 @@ export const jaJPObjects: NonNullable = { } } }, - sys_webhook: { - label: "Webhook", - pluralLabel: "Webhook", - description: "送信 HTTP Webhook サブスクリプション。defineWebhook() またはスタジオエディタで作成し、HTTP コネクタプラグインが実行します。", - fields: { - id: { - label: "Webhook ID" - }, - name: { - label: "名前", - help: "一意の snake_case 名 — ログおよび監査で参照" - }, - label: { - label: "表示名" - }, - object_name: { - label: "オブジェクト", - help: "このウェブフックを発火するイベントのオブジェクト短縮名(空白 = 手動 / API トリガー)" - }, - triggers: { - label: "トリガー", - help: "カンマ区切りのイベントリスト: create,update,delete,undelete,api" - }, - url: { - label: "ターゲット URL", - help: "POST を受信する外部エンドポイント" - }, - method: { - label: "HTTP メソッド", - help: "GET / POST / PUT / PATCH / DELETE" - }, - description: { - label: "説明" - }, - active: { - label: "有効", - help: "無効にするとディスパッチャにスキップされます" - }, - definition_json: { - label: "定義", - help: "シリアライズされた Webhook JSON(@objectstack/spec/automation/webhook 参照)— ヘッダー/認証/リトライ/ペイロード設定を含む" - }, - created_at: { - label: "作成日時" - }, - updated_at: { - label: "更新日時" - } - }, - _views: { - active: { - label: "有効" - }, - inactive: { - label: "無効" - }, - by_object: { - label: "オブジェクト別" - }, - all_webhooks: { - label: "すべて" - } - } - }, sys_metadata: { label: "システムメタデータ", pluralLabel: "システムメタデータ", @@ -3028,8 +2367,8 @@ export const jaJPObjects: NonNullable = { label: "ロック状態", options: { none: "なし", - "no-overlay": "オーバーレイ禁止", - "no-delete": "削除禁止", + nooverlay: "no-overlay", + nodelete: "no-delete", full: "完全ロック" } }, diff --git a/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts index 4521d853e..5ef7b6e62 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts @@ -60,18 +60,123 @@ export const zhCNMetadataForms: NonNullable = fields: { helpText: "添加该对象将存储的列" }, - "fields.name": { - helpText: "snake_case 标识符" - }, "fields.label": { helpText: "展示用标签" }, "fields.type": { helpText: "字段类型" }, + "fields.description": { + helpText: "Developer documentation for this column" + }, + "fields.required": { + helpText: "Must be set on every record" + }, + "fields.unique": { + helpText: "Disallow duplicate values" + }, + "fields.indexed": { + helpText: "Create a database index for faster querying" + }, + "fields.readonly": { + helpText: "Visible but never user-editable" + }, + "fields.immutable": { + helpText: "Editable on create, locked thereafter" + }, + "fields.hidden": { + helpText: "Hidden from default UI" + }, + "fields.searchable": { + helpText: "Include in full-text search" + }, + "fields.sortable": { + helpText: "Allow sorting on this column" + }, + "fields.filterable": { + helpText: "Allow filtering on this column" + }, + "fields.defaultValue": { + helpText: "Default value for new records (JSON literal)" + }, + "fields.placeholder": { + helpText: "Placeholder hint" + }, + "fields.maxLength": { + helpText: "Max characters" + }, + "fields.minLength": { + helpText: "Min characters" + }, + "fields.min": { + helpText: "Minimum value" + }, + "fields.max": { + helpText: "Maximum value" + }, + "fields.precision": { + helpText: "Total digits" + }, + "fields.scale": { + helpText: "Decimal places" + }, + "fields.options": { + helpText: "Available choices" + }, + "fields.options.icon": { + helpText: "Lucide icon name" + }, "fields.reference": { helpText: "目标对象(用于 lookup / master_detail)" }, + "fields.referenceFilter": { + helpText: "CEL filter applied to the picker" + }, + "fields.cascadeDelete": { + helpText: "Delete children when parent is deleted" + }, + "fields.multiple": { + helpText: "Allow selecting multiple records" + }, + "fields.formula": { + helpText: "CEL formula expression" + }, + "fields.returnType": { + helpText: "Result type for formulas" + }, + "fields.summaryType": { + helpText: "Aggregation" + }, + "fields.summaryField": { + helpText: "Field on child object to aggregate" + }, + "fields.displayFormat": { + helpText: "e.g. \"INV-{0000}\"" + }, + "fields.startingNumber": { + helpText: "Starting sequence value" + }, + "fields.language": { + helpText: "Editor language (e.g. sql, javascript)" + }, + "fields.validation": { + helpText: "CEL predicate — must evaluate true" + }, + "fields.errorMessage": { + helpText: "Shown when validation fails" + }, + "fields.audit": { + helpText: "Audit changes to this field" + }, + "fields.trackHistory": { + helpText: "Keep change history" + }, + "fields.pii": { + helpText: "Personally identifiable information" + }, + "fields.encrypted": { + helpText: "Encrypt at rest" + }, capabilities: { helpText: "启用或禁用系统功能" }, @@ -742,58 +847,15 @@ export const zhCNMetadataForms: NonNullable = } } }, - workflow: { - label: "工作流", - sections: { - basics: { - label: "基础信息", - description: "名称与触发条件" - }, - actions: { - label: "执行动作", - description: "满足条件后做什么" - }, - advanced: { - label: "高级设置", - description: "执行顺序与错误处理" - } - }, - fields: { - name: { - helpText: "唯一标识符(snake_case)" - }, - objectName: { - helpText: "触发此工作流的对象" - }, - triggerType: { - helpText: "何时触发:on_create、on_update、on_delete、schedule" - }, - active: { - helpText: "启用或禁用此工作流" - }, - description: { - helpText: "此工作流的用途说明" - }, - criteria: { - helpText: "CEL 表达式:仅当条件为真时执行" - }, - actions: { - helpText: "立即执行的动作(字段更新、邮件、API 调用等)" - }, - timeTriggers: { - helpText: "定时动作(如截止前 1 天发提醒)" - }, - executionOrder: { - helpText: "同时匹配多个工作流时的执行顺序(数字越小越先执行)" - } - } - }, job: { label: "后台作业" }, datasource: { label: "数据源" }, + external_catalog: { + label: "External Catalog" + }, translation: { label: "翻译" }, @@ -817,27 +879,47 @@ export const zhCNMetadataForms: NonNullable = label: "主题", description: "邮件主题模板" }, - body: { - label: "正文", - description: "邮件正文内容" + html_body: { + label: "HTML body", + description: "Rich HTML body. Most clients strip , so use inline styles." + }, + plain_text_body: { + label: "Plain-text body", + description: "Optional plain-text alternative. When omitted, the service strips tags from the HTML body to derive one. Providing one improves spam scoring." + }, + variables: { + label: "Variables", + description: "Declared variables. Rendered as hints in Studio and validated by sendTemplate() when required." + }, + delivery_overrides: { + label: "Delivery overrides", + description: "Optional per-template overrides for From / Reply-To." }, - variables_and_attachments: { - label: "变量与附件", - description: "可注入的变量与附件列表" + status: { + label: "Status" } }, fields: { - id: { - helpText: "唯一标识符(snake_case)" + name: { + helpText: "Dotted snake_case (e.g. auth.password_reset, crm.welcome)" }, - body: { - helpText: "支持变量插值的邮件正文" + locale: { + helpText: "BCP-47 tag — e.g. en-US, zh-CN" }, variables: { helpText: "模板内可引用的变量与默认值" }, - attachments: { - helpText: "附件列表(文件引用或 URL)" + fromOverride: { + helpText: "{ \"name\": \"Acme Sales\", \"address\": \"sales@acme.com\" }" + }, + replyTo: { + helpText: "Reply-To email address" + }, + active: { + helpText: "When unchecked, sendTemplate() returns TEMPLATE_INACTIVE." + }, + isSystem: { + helpText: "Built-in template; tenants may override but should not delete." } } }, diff --git a/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts index 8fdbffa57..6398b436a 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts @@ -56,6 +56,9 @@ export const zhCNObjects: NonNullable = { } }, _views: { + me: { + label: "My Profile" + }, all_users: { label: "全部用户" }, @@ -108,10 +111,28 @@ export const zhCNObjects: NonNullable = { label: "修改邮箱", successMessage: "已发送验证邮件,请前往新邮箱确认。" }, + resend_verification_email: { + label: "Resend Verification Email", + successMessage: "Verification email sent — check your inbox." + }, delete_my_account: { label: "删除我的账号", confirmText: "确定要永久删除您的账户吗?此操作无法撤销——您的所有会话都将被终止,并将按照配置的保留策略移除您拥有的所有数据。", successMessage: "已删除账号" + }, + enable_two_factor: { + label: "Enable Two-Factor Auth", + successMessage: "Two-factor authentication enabled. Scan the QR code or paste the otpauth URI into your authenticator app, then verify a code to complete setup." + }, + disable_two_factor: { + label: "Disable Two-Factor Auth", + confirmText: "Turn off two-factor authentication? Your account will be less secure.", + successMessage: "Two-factor authentication disabled." + }, + generate_backup_codes: { + label: "Regenerate Backup Codes", + confirmText: "Generate a new set of backup codes? Any previously generated codes will stop working.", + successMessage: "New backup codes generated — save them somewhere safe." } } }, @@ -331,6 +352,11 @@ export const zhCNObjects: NonNullable = { label: "退出组织", confirmText: "要退出该组织吗?退出后你将失去对其所有资源的访问权限。", successMessage: "你已退出该组织" + }, + change_slug: { + label: "Change Slug", + confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?", + successMessage: "Organization slug changed" } } }, @@ -361,6 +387,11 @@ export const zhCNObjects: NonNullable = { } } }, + _views: { + mine: { + label: "My Memberships" + } + }, _actions: { add_member: { label: "添加成员", @@ -760,6 +791,10 @@ export const zhCNObjects: NonNullable = { backup_codes: { label: "备用恢复码", help: "JSON 序列化的备用恢复码" + }, + verified: { + label: "Verified", + help: "Whether the enrollment was confirmed with a valid TOTP code (managed by better-auth)" } }, _views: { @@ -998,6 +1033,9 @@ export const zhCNObjects: NonNullable = { } }, _views: { + mine: { + label: "My Applications" + }, active: { label: "启用" }, @@ -1183,473 +1221,6 @@ export const zhCNObjects: NonNullable = { } } }, - sys_role: { - label: "角色", - pluralLabel: "角色", - description: "用于 RBAC 访问控制的角色定义", - fields: { - label: { - label: "显示名称" - }, - name: { - label: "API 名称", - help: "角色的唯一机器名称(例如 admin、editor、viewer)" - }, - description: { - label: "描述" - }, - permissions: { - label: "权限", - help: "权限字符串数组的 JSON 序列化内容" - }, - active: { - label: "启用" - }, - is_default: { - label: "默认角色", - help: "自动分配给新用户" - }, - id: { - label: "角色 ID" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - }, - _views: { - active: { - label: "启用" - }, - default_roles: { - label: "默认" - }, - custom: { - label: "自定义" - }, - all_roles: { - label: "全部" - } - }, - _actions: { - activate_role: { - label: "激活角色", - successMessage: "角色已激活" - }, - deactivate_role: { - label: "停用角色", - confirmText: "确定要停用此角色吗?拥有该角色的用户仍保留其分配,但在重新激活之前该角色将不再授予权限。", - successMessage: "角色已停用" - }, - set_default_role: { - label: "设为默认", - confirmText: "将此角色设为新用户的默认角色吗?现有用户不受影响。", - successMessage: "已更新默认角色" - }, - clone_role: { - label: "克隆角色", - successMessage: "已克隆角色" - } - } - }, - sys_permission_set: { - label: "权限集", - pluralLabel: "权限集", - description: "用于精细化访问控制的命名权限分组", - fields: { - label: { - label: "显示名称" - }, - name: { - label: "API 名称", - help: "权限集的唯一机器名称" - }, - description: { - label: "描述" - }, - object_permissions: { - label: "对象权限", - help: "对象级 CRUD 权限的 JSON 序列化内容" - }, - field_permissions: { - label: "字段权限", - help: "字段级读写权限的 JSON 序列化内容" - }, - system_permissions: { - label: "系统权限", - help: "系统能力名称的 JSON 序列化数组(例如 [\"setup.access\",\"studio.access\",\"manage_users\"])" - }, - row_level_security: { - label: "行级安全", - help: "行级安全策略的 JSON 序列化数组(USING/CHECK 子句)" - }, - tab_permissions: { - label: "标签页权限", - help: "应用标签页可见性的 JSON 序列化映射(visible | hidden | default_on | default_off)" - }, - active: { - label: "启用" - }, - id: { - label: "权限集 ID" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - }, - _views: { - active: { - label: "启用" - }, - inactive: { - label: "停用" - }, - all_permsets: { - label: "全部" - } - }, - _actions: { - activate_permission_set: { - label: "激活", - successMessage: "权限集已激活" - }, - deactivate_permission_set: { - label: "停用", - confirmText: "确定要停用此权限集吗?现有分配仍将保留,但在重新激活之前将不再授予访问权限。", - successMessage: "权限集已停用" - }, - clone_permission_set: { - label: "克隆", - successMessage: "已克隆权限集" - } - } - }, - sys_user_permission_set: { - label: "用户权限集", - pluralLabel: "用户权限集", - description: "将权限集直接分配给用户(可按组织范围限定)。", - fields: { - id: { - label: "分配 ID", - help: "该分配记录的 UUID。" - }, - user_id: { - label: "用户", - help: "指向 sys_user 的外键。" - }, - permission_set_id: { - label: "权限集", - help: "指向 sys_permission_set 的外键。" - }, - organization_id: { - label: "组织", - help: "可选的组织范围。NULL = 在所有组织上下文中都生效。" - }, - granted_by: { - label: "授权人", - help: "授予该权限集的用户。" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - } - }, - sys_role_permission_set: { - label: "角色权限集", - pluralLabel: "角色权限集", - description: "将权限集绑定到角色。", - fields: { - id: { - label: "绑定 ID", - help: "角色-权限集绑定记录的 UUID。" - }, - role_id: { - label: "角色", - help: "指向 sys_role 的外键。" - }, - permission_set_id: { - label: "权限集", - help: "指向 sys_permission_set 的外键。" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - } - }, - sys_record_share: { - label: "记录共享", - pluralLabel: "记录共享", - description: "按记录粒度的共享授权——在 OWD 基础上提供显式访问", - fields: { - id: { - label: "共享 ID" - }, - object_name: { - label: "对象", - help: "被共享记录的短对象名" - }, - record_id: { - label: "记录", - help: "object_name 对应对象内该共享记录的主键" - }, - recipient_type: { - label: "接收方类型", - help: "持有该授权的主体类型", - options: { - user: "用户", - group: "组", - role: "角色", - role_and_subordinates: "角色及下级", - guest: "访客" - } - }, - recipient_id: { - label: "接收方", - help: "获得访问权限的用户 / 组 / 角色 ID" - }, - access_level: { - label: "访问级别", - help: "接收方可以执行的操作——read | edit | full(转移 / 共享 / 删除)", - options: { - read: "读取", - edit: "编辑", - full: "完全访问" - } - }, - source: { - label: "来源", - help: "该授权存在的原因——供规则求值器对账使用", - options: { - manual: "手动", - rule: "规则", - team: "团队", - inherited: "继承" - } - }, - source_id: { - label: "来源 ID", - help: "当 source != manual 时,对应规则名 / 团队 ID" - }, - granted_by: { - label: "授权人", - help: "创建该授权的用户(仅手动授权)" - }, - reason: { - label: "原因", - help: "可选的自由文本说明,会展示给接收方" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - }, - _views: { - granted_to_me: { - label: "授予我的" - }, - granted_by_me: { - label: "我授予的" - }, - by_object: { - label: "按对象" - }, - manual_grants: { - label: "手动授权" - }, - rule_grants: { - label: "规则授权" - }, - all_shares: { - label: "全部" - } - } - }, - sys_sharing_rule: { - label: "共享规则", - pluralLabel: "共享规则", - description: "声明式共享规则,会自动生成 sys_record_share 授权。可在代码中通过 defineSharingRule() 编写,或在 Studio 条件构建器中维护。", - fields: { - id: { - label: "规则 ID" - }, - organization_id: { - label: "组织", - help: "拥有该规则的租户;null = 全局" - }, - name: { - label: "名称", - help: "唯一的 snake_case 规则名称" - }, - label: { - label: "显示标签" - }, - description: { - label: "描述" - }, - object_name: { - label: "对象", - help: "短对象名(例如 opportunity、account)" - }, - criteria_json: { - label: "条件(FilterCondition JSON)", - help: "针对 object_name 记录匹配的 JSON FilterCondition。为空表示匹配全部。" - }, - recipient_type: { - label: "接收方类型", - help: "接收访问权限的主体类型——求值时会展开为用户授权。`department` 会沿 parent_department_id 树展开;`team` 为扁平结构(better-auth)。", - options: { - user: "用户", - team: "团队", - department: "部门", - role: "角色", - queue: "队列" - } - }, - recipient_id: { - label: "接收方", - help: "根据 recipient_type 填写 department id / team id / role name / queue name / user id" - }, - access_level: { - label: "访问级别", - options: { - read: "读取", - edit: "编辑", - full: "完全访问" - } - }, - active: { - label: "启用", - help: "只有启用的规则才会参与生命周期求值" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - }, - _views: { - active: { - label: "启用" - }, - inactive: { - label: "停用" - }, - by_object: { - label: "按对象" - }, - all_rules: { - label: "全部" - } - } - }, - sys_share_link: { - label: "共享链接", - pluralLabel: "共享链接", - description: "授予对单条记录访问权限的不透明能力令牌。类似 Notion/Figma 的公开链接共享。", - fields: { - id: { - label: "链接 ID" - }, - token: { - label: "令牌", - help: "URL 安全的不透明随机令牌(≥ 22 个字符)。本记录中唯一的机密信息。" - }, - object_name: { - label: "对象", - help: "所共享记录的对象短名称(例如 ai_conversation、contracts_contract)" - }, - record_id: { - label: "记录", - help: "object_name 内所共享记录的主键" - }, - permission: { - label: "权限", - help: "链接持有者可对该记录执行的操作", - options: { - view: "查看", - comment: "评论", - edit: "编辑" - } - }, - audience: { - label: "受众", - help: "在令牌校验之上额外施加的访问限制层", - options: { - public: "公开(可被索引)", - link_only: "任何持有链接的人", - signed_in: "已登录用户", - email: "指定邮箱" - } - }, - expires_at: { - label: "过期时间", - help: "设置后,超过此时间点 resolveToken 将返回 null" - }, - email_allowlist: { - label: "邮箱白名单", - help: "当 audience=email 时校验的小写邮箱地址" - }, - password_hash: { - label: "密码哈希", - help: "Argon2/bcrypt 哈希值。设置后,界面会在呈现内容前提示输入密码。" - }, - redact_fields: { - label: "按链接脱敏字段", - help: "在对象默认脱敏集之上,从响应中额外剔除的字段" - }, - label: { - label: "标签", - help: "在共享对话框中显示的自由文本(例如 \"ACME Q3 合同\")" - }, - revoked_at: { - label: "撤销时间", - help: "设置后,该链接将被永久停用" - }, - created_by: { - label: "创建人", - help: "链接的签发者" - }, - created_at: { - label: "创建时间" - }, - last_used_at: { - label: "最近使用时间", - help: "由 resolveToken 标记;仪表盘据此高亮显示活跃链接" - }, - use_count: { - label: "使用次数", - help: "每次成功解析时由 resolveToken 递增" - } - }, - _views: { - active_links: { - label: "活跃" - }, - by_me: { - label: "我创建的" - }, - revoked: { - label: "已撤销" - }, - all_links: { - label: "全部" - } - } - }, sys_audit_log: { label: "审计日志", pluralLabel: "审计日志", @@ -1979,28 +1550,26 @@ export const zhCNObjects: NonNullable = { id: { label: "通知 ID" }, - recipient_id: { - label: "接收方", - help: "接收该通知的用户" + topic: { + label: "Topic", + help: "Notification topic, e.g. task.assigned, collab.mention" }, - type: { - label: "类型", - help: "通知类别——决定图标和排序优先级", + payload: { + label: "Payload", + help: "Template inputs carried to channels (title/body/url/actor/source/…)" + }, + severity: { + label: "Severity", + help: "Severity hint for rendering / filtering", options: { - mention: "提及", - assignment: "分配", - comment_reply: "评论回复", - lead_converted: "线索转化", - task_due: "任务到期", - system: "系统" + info: "info", + warning: "warning", + critical: "critical" } }, - title: { - label: "标题" - }, - body: { - label: "正文", - help: "可选的补充文本(单行摘要)" + dedup_key: { + label: "Dedup Key", + help: "Idempotency key within a topic window; a repeat emit is a no-op" }, source_object: { label: "来源对象", @@ -2010,50 +1579,20 @@ export const zhCNObjects: NonNullable = { label: "来源记录", help: "source_object 中的记录 ID" }, - url: { - label: "深度链接", - help: "点击后跳转的可选 URL" - }, actor_id: { label: "执行人", help: "触发该通知的用户(提及人、分配人)" }, - actor_name: { - label: "执行人名称" - }, - is_read: { - label: "已读", - help: "接收人确认后为 true" - }, - read_at: { - label: "读取时间" - }, created_at: { label: "创建时间" - }, - updated_at: { - label: "更新时间" } }, _views: { - unread: { - label: "未读" - }, - mine: { - label: "我的" - }, - all_notifications: { - label: "全部" - } - }, - _actions: { - mark_read: { - label: "标记为已读", - successMessage: "已标记为已读" + recent: { + label: "Recent" }, - mark_unread: { - label: "标记为未读", - successMessage: "已标记为未读" + by_topic: { + label: "By Topic" } } }, @@ -2331,142 +1870,6 @@ export const zhCNObjects: NonNullable = { } } }, - sys_approval_request: { - label: "审批请求", - pluralLabel: "审批请求", - description: "按提交记录跟踪的实时审批实例", - fields: { - id: { - label: "请求 ID" - }, - organization_id: { - label: "组织", - help: "拥有该审批请求的租户(从提交方上下文传播)" - }, - process_name: { - label: "来源", - help: "请求来源 —— 节点驱动的审批为 `flow:`" - }, - object_name: { - label: "对象" - }, - record_id: { - label: "记录 ID" - }, - submitter_id: { - label: "提交人" - }, - submitter_comment: { - label: "提交备注" - }, - status: { - label: "状态", - help: "请求的生命周期状态", - options: { - pending: "待处理", - approved: "已批准", - rejected: "已拒绝", - recalled: "已撤回" - } - }, - current_step: { - label: "当前步骤", - help: "当前等待审批的步骤机器名称" - }, - current_step_index: { - label: "当前步骤索引" - }, - pending_approvers: { - label: "待审批人", - help: "可在当前步骤执行操作的用户 ID,逗号分隔" - }, - payload_json: { - label: "快照", - help: "提交时的记录快照" - }, - process_hash: { - label: "流程哈希", - help: "提交时审批流程主体的 sha256(ADR-0009 执行固定)。通过 sys_metadata_history 解析,因此流程升级不会影响进行中的请求。" - }, - completed_at: { - label: "完成时间" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - }, - _views: { - my_pending: { - label: "我的待办" - }, - submitted_by_me: { - label: "我提交的" - }, - completed: { - label: "已完成" - }, - all_requests: { - label: "全部" - } - } - }, - sys_approval_action: { - label: "审批动作", - pluralLabel: "审批动作", - description: "追加写入的审批操作审计记录", - fields: { - id: { - label: "动作 ID" - }, - organization_id: { - label: "组织", - help: "拥有该动作的租户(与父请求保持一致)" - }, - request_id: { - label: "请求" - }, - step_name: { - label: "步骤", - help: "执行该动作时对应步骤的机器名称" - }, - step_index: { - label: "步骤索引" - }, - action: { - label: "操作", - options: { - submit: "提交", - approve: "批准", - reject: "拒绝", - recall: "撤回", - escalate: "升级" - } - }, - actor_id: { - label: "执行人" - }, - comment: { - label: "评论" - }, - created_at: { - label: "创建时间" - } - }, - _views: { - recent: { - label: "最近" - }, - by_actor: { - label: "按执行人" - }, - all_actions: { - label: "全部" - } - } - }, sys_job: { label: "后台任务", pluralLabel: "后台任务", @@ -2660,70 +2063,6 @@ export const zhCNObjects: NonNullable = { } } }, - sys_webhook: { - label: "Webhook", - pluralLabel: "Webhook", - description: "外发 HTTP Webhook 订阅。可在代码中通过 defineWebhook() 编写,或在 Studio 编辑器中维护;由 HTTP 连接器插件执行。", - fields: { - id: { - label: "Webhook ID" - }, - name: { - label: "名称", - help: "唯一的 snake_case 名称——用于日志和审计引用" - }, - label: { - label: "显示标签" - }, - object_name: { - label: "对象", - help: "触发该 Webhook 的短对象名(留空 = 手动 / API 触发)" - }, - triggers: { - label: "触发器", - help: "以逗号分隔的事件列表:create,update,delete,undelete,api" - }, - url: { - label: "目标 URL", - help: "接收 POST 请求的外部端点" - }, - method: { - label: "HTTP 方法", - help: "GET / POST / PUT / PATCH / DELETE" - }, - description: { - label: "描述" - }, - active: { - label: "启用", - help: "停用的 Webhook 会被调度器跳过" - }, - definition_json: { - label: "定义", - help: "序列化的 Webhook JSON(参见 @objectstack/spec/automation/webhook)——包含完整的 headers/auth/retry/payload 配置" - }, - created_at: { - label: "创建时间" - }, - updated_at: { - label: "更新时间" - } - }, - _views: { - active: { - label: "启用" - }, - inactive: { - label: "停用" - }, - by_object: { - label: "按对象" - }, - all_webhooks: { - label: "全部" - } - } - }, sys_metadata: { label: "系统元数据", pluralLabel: "系统元数据", @@ -3028,8 +2367,8 @@ export const zhCNObjects: NonNullable = { label: "锁定状态", options: { none: "无", - "no-overlay": "禁止覆盖", - "no-delete": "禁止删除", + nooverlay: "no-overlay", + nodelete: "no-delete", full: "完全锁定" } }, diff --git a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts new file mode 100644 index 000000000..3ef5114ab --- /dev/null +++ b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Build-time only config for `os i18n extract` (ADR-0029 D8). Not deployed. + * The plugin owns the i18n extraction for the objects it owns; the + * `translations` baseline is this plugin's OWN generated bundles so re-running + * `--merge` preserves every hand-translated string. (Initial zh-CN/ja-JP/es-ES + * strings were seeded from @objectstack/platform-objects.) + * + * os i18n extract packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts \ + * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only \ + * --out=packages/plugins/plugin-approvals/src/translations + */ + +import { defineStack } from '@objectstack/spec'; +import { SysApprovalRequest } from '../src/sys-approval-request.object.js'; +import { SysApprovalAction } from '../src/sys-approval-action.object.js'; +import { enObjects } from '../src/translations/en.objects.generated.js'; +import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js'; +import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js'; +import { esESObjects } from '../src/translations/es-ES.objects.generated.js'; + +export default defineStack({ + name: 'plugin-approvals-i18n-extract', + objects: [SysApprovalRequest, SysApprovalAction] as any, + translations: [ + { en: { objects: enObjects } }, + { 'zh-CN': { objects: zhCNObjects } }, + { 'ja-JP': { objects: jaJPObjects } }, + { 'es-ES': { objects: esESObjects } }, + ], +}); diff --git a/packages/plugins/plugin-approvals/src/approvals-plugin.ts b/packages/plugins/plugin-approvals/src/approvals-plugin.ts index a500d3b53..9509ee4aa 100644 --- a/packages/plugins/plugin-approvals/src/approvals-plugin.ts +++ b/packages/plugins/plugin-approvals/src/approvals-plugin.ts @@ -66,6 +66,21 @@ export class ApprovalsServicePlugin implements Plugin { }, ], }); + // ADR-0029 D8 — contribute this plugin's object translations to the i18n + // service on kernel:ready (the i18n plugin may register after this one). + if (typeof (ctx as any).hook === 'function') { + (ctx as any).hook('kernel:ready', async () => { + try { + const i18n = ctx.getService('i18n'); + if (i18n && typeof i18n.loadTranslations === 'function') { + const { ApprovalsTranslations } = await import('./translations/index.js'); + for (const [locale, data] of Object.entries(ApprovalsTranslations)) { + i18n.loadTranslations(locale, data as Record); + } + } + } catch { /* i18n optional */ } + }); + } ctx.logger.info('ApprovalsServicePlugin: schemas registered'); } diff --git a/packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts b/packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts new file mode 100644 index 000000000..a99416620 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts @@ -0,0 +1,156 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'en'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const enObjects: NonNullable = { + sys_approval_request: { + label: "Approval Request", + pluralLabel: "Approval Requests", + description: "Live approval instance tracked per submission", + fields: { + id: { + label: "Request ID" + }, + organization_id: { + label: "Organization", + help: "Tenant that owns this approval request (propagated from submitter context)" + }, + process_name: { + label: "Source", + help: "Origin of the request — `flow:` for node-driven approvals" + }, + object_name: { + label: "Object" + }, + record_id: { + label: "Record ID" + }, + submitter_id: { + label: "Submitter" + }, + submitter_comment: { + label: "Submitter Comment" + }, + status: { + label: "Status", + help: "Lifecycle state of the request", + options: { + pending: "pending", + approved: "approved", + rejected: "rejected", + recalled: "recalled" + } + }, + current_step: { + label: "Current Step", + help: "Machine name of the step awaiting approval" + }, + current_step_index: { + label: "Current Step Index" + }, + pending_approvers: { + label: "Pending Approvers", + help: "Comma-separated user ids who can act on the current step" + }, + payload_json: { + label: "Snapshot", + help: "Record snapshot at submission time" + }, + flow_run_id: { + label: "Flow Run", + help: "Suspended automation run id this request gates (ADR-0019). The decision resumes it." + }, + flow_node_id: { + label: "Flow Node", + help: "Approval node id within the flow that opened this request (ADR-0019)." + }, + node_config_json: { + label: "Node Config", + help: "Snapshot of the Approval node config (approvers/behavior) for node-driven requests (ADR-0019)." + }, + completed_at: { + label: "Completed At" + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + }, + _views: { + my_pending: { + label: "My Pending" + }, + submitted_by_me: { + label: "I Submitted" + }, + completed: { + label: "Completed" + }, + all_requests: { + label: "All" + } + } + }, + sys_approval_action: { + label: "Approval Action", + pluralLabel: "Approval Actions", + description: "Append-only audit trail for approval actions", + fields: { + id: { + label: "Action ID" + }, + organization_id: { + label: "Organization", + help: "Tenant that owns this action (mirrors the parent request)" + }, + request_id: { + label: "Request" + }, + step_name: { + label: "Step", + help: "Machine name of the step at the time of the action" + }, + step_index: { + label: "Step Index" + }, + action: { + label: "Action", + options: { + submit: "submit", + approve: "approve", + reject: "reject", + recall: "recall", + escalate: "escalate" + } + }, + actor_id: { + label: "Actor" + }, + comment: { + label: "Comment" + }, + created_at: { + label: "Created At" + } + }, + _views: { + recent: { + label: "Recent" + }, + by_actor: { + label: "By Actor" + }, + all_actions: { + label: "All" + } + } + } +}; diff --git a/packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts b/packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts new file mode 100644 index 000000000..0bc73c722 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/es-ES.objects.generated.ts @@ -0,0 +1,156 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const esESObjects: NonNullable = { + sys_approval_request: { + label: "Solicitud de aprobación", + pluralLabel: "Solicitudes de aprobación", + description: "Instancia activa de aprobación registrada por envío", + fields: { + id: { + label: "ID de solicitud" + }, + organization_id: { + label: "Organización", + help: "Tenant que posee esta solicitud de aprobación (propagado desde el contexto del solicitante)." + }, + process_name: { + label: "Origen", + help: "Origen de la solicitud — `flow:` para aprobaciones por nodo" + }, + object_name: { + label: "Objeto" + }, + record_id: { + label: "ID de registro" + }, + submitter_id: { + label: "Solicitante" + }, + submitter_comment: { + label: "Comentario del solicitante" + }, + status: { + label: "Estado", + help: "Estado del ciclo de vida de la solicitud.", + options: { + pending: "Pendiente", + approved: "Aprobada", + rejected: "Rechazada", + recalled: "Retirada" + } + }, + current_step: { + label: "Paso actual", + help: "Nombre técnico del paso pendiente de aprobación." + }, + current_step_index: { + label: "Índice del paso actual" + }, + pending_approvers: { + label: "Aprobadores pendientes", + help: "ID de usuario separados por comas que pueden actuar en el paso actual." + }, + payload_json: { + label: "Instantánea", + help: "Instantánea del registro en el momento del envío." + }, + flow_run_id: { + label: "Flow Run", + help: "Suspended automation run id this request gates (ADR-0019). The decision resumes it." + }, + flow_node_id: { + label: "Flow Node", + help: "Approval node id within the flow that opened this request (ADR-0019)." + }, + node_config_json: { + label: "Node Config", + help: "Snapshot of the Approval node config (approvers/behavior) for node-driven requests (ADR-0019)." + }, + completed_at: { + label: "Completado el" + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + }, + _views: { + my_pending: { + label: "Mis pendientes" + }, + submitted_by_me: { + label: "Enviadas por mí" + }, + completed: { + label: "Completadas" + }, + all_requests: { + label: "Todas" + } + } + }, + sys_approval_action: { + label: "Acción de aprobación", + pluralLabel: "Acciones de aprobación", + description: "Registro de auditoría append-only para acciones de aprobación", + fields: { + id: { + label: "ID de acción" + }, + organization_id: { + label: "Organización", + help: "Tenant que posee esta acción (refleja la solicitud principal)." + }, + request_id: { + label: "Solicitud" + }, + step_name: { + label: "Paso", + help: "Nombre técnico del paso en el momento de la acción." + }, + step_index: { + label: "Índice del paso" + }, + action: { + label: "Acción", + options: { + submit: "Enviar", + approve: "Aprobar", + reject: "Rechazar", + recall: "Retirar", + escalate: "Escalar" + } + }, + actor_id: { + label: "Actor" + }, + comment: { + label: "Comentario" + }, + created_at: { + label: "Creado el" + } + }, + _views: { + recent: { + label: "Recientes" + }, + by_actor: { + label: "Por actor" + }, + all_actions: { + label: "Todas" + } + } + } +}; diff --git a/packages/plugins/plugin-approvals/src/translations/index.ts b/packages/plugins/plugin-approvals/src/translations/index.ts new file mode 100644 index 000000000..d448d5f63 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * ApprovalsTranslations — i18n bundle owned by this plugin (ADR-0029 D8). + * + * Object label/field/view/action translations for the sys_* objects this + * plugin owns. Loaded at runtime via the plugin's `kernel:ready` hook + * (`i18n.loadTranslations`). Regenerate with `os i18n extract` against + * `scripts/i18n-extract.config.ts`. + */ + +import type { TranslationBundle } from '@objectstack/spec/system'; +import { enObjects } from './en.objects.generated.js'; +import { zhCNObjects } from './zh-CN.objects.generated.js'; +import { jaJPObjects } from './ja-JP.objects.generated.js'; +import { esESObjects } from './es-ES.objects.generated.js'; + +export const ApprovalsTranslations: TranslationBundle = { + en: { objects: enObjects }, + 'zh-CN': { objects: zhCNObjects }, + 'ja-JP': { objects: jaJPObjects }, + 'es-ES': { objects: esESObjects }, +}; diff --git a/packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts b/packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts new file mode 100644 index 000000000..e4dbfeb98 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/ja-JP.objects.generated.ts @@ -0,0 +1,156 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const jaJPObjects: NonNullable = { + sys_approval_request: { + label: "承認リクエスト", + pluralLabel: "承認リクエスト", + description: "送信ごとに追跡されるライブ承認インスタンス", + fields: { + id: { + label: "リクエスト ID" + }, + organization_id: { + label: "組織", + help: "この承認リクエストを所有するテナント(送信者コンテキストから伝播)" + }, + process_name: { + label: "ソース", + help: "リクエストの発生元 — ノード駆動の承認では `flow:`" + }, + object_name: { + label: "オブジェクト" + }, + record_id: { + label: "レコード ID" + }, + submitter_id: { + label: "送信者" + }, + submitter_comment: { + label: "送信者コメント" + }, + status: { + label: "ステータス", + help: "リクエストのライフサイクル状態", + options: { + pending: "保留中", + approved: "承認済み", + rejected: "却下済み", + recalled: "取り消し済み" + } + }, + current_step: { + label: "現在のステップ", + help: "承認待ちのステップの機械名" + }, + current_step_index: { + label: "現在のステップ番号" + }, + pending_approvers: { + label: "承認待ち承認者", + help: "現在のステップを処理できるユーザー ID のカンマ区切りリスト" + }, + payload_json: { + label: "スナップショット", + help: "送信時のレコードスナップショット" + }, + flow_run_id: { + label: "Flow Run", + help: "Suspended automation run id this request gates (ADR-0019). The decision resumes it." + }, + flow_node_id: { + label: "Flow Node", + help: "Approval node id within the flow that opened this request (ADR-0019)." + }, + node_config_json: { + label: "Node Config", + help: "Snapshot of the Approval node config (approvers/behavior) for node-driven requests (ADR-0019)." + }, + completed_at: { + label: "完了日時" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + }, + _views: { + my_pending: { + label: "自分の保留中" + }, + submitted_by_me: { + label: "自分が送信" + }, + completed: { + label: "完了済み" + }, + all_requests: { + label: "すべて" + } + } + }, + sys_approval_action: { + label: "承認アクション", + pluralLabel: "承認アクション", + description: "承認アクションの追記専用監査証跡", + fields: { + id: { + label: "アクション ID" + }, + organization_id: { + label: "組織", + help: "このアクションを所有するテナント(親リクエストと同じ)" + }, + request_id: { + label: "リクエスト" + }, + step_name: { + label: "ステップ", + help: "アクション時点のステップの機械名" + }, + step_index: { + label: "ステップ番号" + }, + action: { + label: "アクション", + options: { + submit: "申請", + approve: "承認", + reject: "却下", + recall: "取消", + escalate: "エスカレーション" + } + }, + actor_id: { + label: "操作者" + }, + comment: { + label: "コメント" + }, + created_at: { + label: "作成日時" + } + }, + _views: { + recent: { + label: "最近" + }, + by_actor: { + label: "操作者別" + }, + all_actions: { + label: "すべて" + } + } + } +}; diff --git a/packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts b/packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts new file mode 100644 index 000000000..02cf827d4 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts @@ -0,0 +1,156 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const zhCNObjects: NonNullable = { + sys_approval_request: { + label: "审批请求", + pluralLabel: "审批请求", + description: "按提交记录跟踪的实时审批实例", + fields: { + id: { + label: "请求 ID" + }, + organization_id: { + label: "组织", + help: "拥有该审批请求的租户(从提交方上下文传播)" + }, + process_name: { + label: "来源", + help: "请求来源 —— 节点驱动的审批为 `flow:`" + }, + object_name: { + label: "对象" + }, + record_id: { + label: "记录 ID" + }, + submitter_id: { + label: "提交人" + }, + submitter_comment: { + label: "提交备注" + }, + status: { + label: "状态", + help: "请求的生命周期状态", + options: { + pending: "待处理", + approved: "已批准", + rejected: "已拒绝", + recalled: "已撤回" + } + }, + current_step: { + label: "当前步骤", + help: "当前等待审批的步骤机器名称" + }, + current_step_index: { + label: "当前步骤索引" + }, + pending_approvers: { + label: "待审批人", + help: "可在当前步骤执行操作的用户 ID,逗号分隔" + }, + payload_json: { + label: "快照", + help: "提交时的记录快照" + }, + flow_run_id: { + label: "Flow Run", + help: "Suspended automation run id this request gates (ADR-0019). The decision resumes it." + }, + flow_node_id: { + label: "Flow Node", + help: "Approval node id within the flow that opened this request (ADR-0019)." + }, + node_config_json: { + label: "Node Config", + help: "Snapshot of the Approval node config (approvers/behavior) for node-driven requests (ADR-0019)." + }, + completed_at: { + label: "完成时间" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + }, + _views: { + my_pending: { + label: "我的待办" + }, + submitted_by_me: { + label: "我提交的" + }, + completed: { + label: "已完成" + }, + all_requests: { + label: "全部" + } + } + }, + sys_approval_action: { + label: "审批动作", + pluralLabel: "审批动作", + description: "追加写入的审批操作审计记录", + fields: { + id: { + label: "动作 ID" + }, + organization_id: { + label: "组织", + help: "拥有该动作的租户(与父请求保持一致)" + }, + request_id: { + label: "请求" + }, + step_name: { + label: "步骤", + help: "执行该动作时对应步骤的机器名称" + }, + step_index: { + label: "步骤索引" + }, + action: { + label: "操作", + options: { + submit: "提交", + approve: "批准", + reject: "拒绝", + recall: "撤回", + escalate: "升级" + } + }, + actor_id: { + label: "执行人" + }, + comment: { + label: "评论" + }, + created_at: { + label: "创建时间" + } + }, + _views: { + recent: { + label: "最近" + }, + by_actor: { + label: "按执行人" + }, + all_actions: { + label: "全部" + } + } + } +}; diff --git a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts new file mode 100644 index 000000000..ba77d7d73 --- /dev/null +++ b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Build-time only config for `os i18n extract` (ADR-0029 D8). Not deployed. + * The plugin owns the i18n extraction for the objects it owns; the + * `translations` baseline is this plugin's OWN generated bundles so re-running + * `--merge` preserves every hand-translated string. (Initial zh-CN/ja-JP/es-ES + * strings were seeded from @objectstack/platform-objects.) + * + * os i18n extract packages/plugins/plugin-security/scripts/i18n-extract.config.ts \ + * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only \ + * --out=packages/plugins/plugin-security/src/translations + */ + +import { defineStack } from '@objectstack/spec'; +import { SysRole, SysPermissionSet, SysUserPermissionSet, SysRolePermissionSet } from '../src/objects/index.js'; +import { enObjects } from '../src/translations/en.objects.generated.js'; +import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js'; +import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js'; +import { esESObjects } from '../src/translations/es-ES.objects.generated.js'; + +export default defineStack({ + name: 'plugin-security-i18n-extract', + objects: [SysRole, SysPermissionSet, SysUserPermissionSet, SysRolePermissionSet] as any, + translations: [ + { en: { objects: enObjects } }, + { 'zh-CN': { objects: zhCNObjects } }, + { 'ja-JP': { objects: jaJPObjects } }, + { 'es-ES': { objects: esESObjects } }, + ], +}); diff --git a/packages/plugins/plugin-security/src/security-plugin.ts b/packages/plugins/plugin-security/src/security-plugin.ts index 6c847c8c3..e02916a40 100644 --- a/packages/plugins/plugin-security/src/security-plugin.ts +++ b/packages/plugins/plugin-security/src/security-plugin.ts @@ -148,6 +148,22 @@ export class SecurityPlugin implements Plugin { ], }); + // ADR-0029 D8 — contribute this plugin's object translations to the i18n + // service on kernel:ready (the i18n plugin may register after this one). + if (typeof (ctx as any).hook === 'function') { + (ctx as any).hook('kernel:ready', async () => { + try { + const i18n = ctx.getService('i18n'); + if (i18n && typeof i18n.loadTranslations === 'function') { + const { SecurityTranslations } = await import('./translations/index.js'); + for (const [locale, data] of Object.entries(SecurityTranslations)) { + i18n.loadTranslations(locale, data as Record); + } + } + } catch { /* i18n optional */ } + }); + } + ctx.logger.info('Security Plugin initialized', { defaultPermissionSets: this.bootstrapPermissionSets.map((p) => p.name), }); diff --git a/packages/plugins/plugin-security/src/translations/en.objects.generated.ts b/packages/plugins/plugin-security/src/translations/en.objects.generated.ts new file mode 100644 index 000000000..406d78abb --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/en.objects.generated.ts @@ -0,0 +1,216 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'en'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const enObjects: NonNullable = { + sys_role: { + label: "Role", + pluralLabel: "Roles", + description: "Role definitions for RBAC access control", + fields: { + label: { + label: "Display Name" + }, + name: { + label: "API Name", + help: "Unique machine name for the role (e.g. admin, editor, viewer)" + }, + description: { + label: "Description" + }, + permissions: { + label: "Permissions", + help: "JSON-serialized array of permission strings" + }, + active: { + label: "Active" + }, + is_default: { + label: "Default Role", + help: "Automatically assigned to new users" + }, + id: { + label: "Role ID" + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + }, + _views: { + active: { + label: "Active" + }, + default_roles: { + label: "Default" + }, + custom: { + label: "Custom" + }, + all_roles: { + label: "All" + } + }, + _actions: { + activate_role: { + label: "Activate Role", + successMessage: "Role activated" + }, + deactivate_role: { + label: "Deactivate Role", + confirmText: "Deactivate this role? Users with the role keep their assignment but the role stops granting permissions until re-activated.", + successMessage: "Role deactivated" + }, + set_default_role: { + label: "Set as Default", + confirmText: "Make this the default role for new users? Existing users are unaffected.", + successMessage: "Default role updated" + }, + clone_role: { + label: "Clone Role", + successMessage: "Role cloned" + } + } + }, + sys_permission_set: { + label: "Permission Set", + pluralLabel: "Permission Sets", + description: "Named permission groupings for fine-grained access control", + fields: { + label: { + label: "Display Name" + }, + name: { + label: "API Name", + help: "Unique machine name for the permission set" + }, + description: { + label: "Description" + }, + object_permissions: { + label: "Object Permissions", + help: "JSON-serialized object-level CRUD permissions" + }, + field_permissions: { + label: "Field Permissions", + help: "JSON-serialized field-level read/write permissions" + }, + system_permissions: { + label: "System Permissions", + help: "JSON-serialized array of system capability names (e.g. [\"setup.access\",\"studio.access\",\"manage_users\"])" + }, + row_level_security: { + label: "Row-Level Security", + help: "JSON-serialized array of row-level security policies (USING/CHECK clauses)" + }, + tab_permissions: { + label: "Tab Permissions", + help: "JSON-serialized map of app tab visibility (visible | hidden | default_on | default_off)" + }, + active: { + label: "Active" + }, + id: { + label: "Permission Set ID" + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + }, + _views: { + active: { + label: "Active" + }, + inactive: { + label: "Inactive" + }, + all_permsets: { + label: "All" + } + }, + _actions: { + activate_permission_set: { + label: "Activate", + successMessage: "Permission set activated" + }, + deactivate_permission_set: { + label: "Deactivate", + confirmText: "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated.", + successMessage: "Permission set deactivated" + }, + clone_permission_set: { + label: "Clone", + successMessage: "Permission set cloned" + } + } + }, + sys_user_permission_set: { + label: "User Permission Set", + pluralLabel: "User Permission Sets", + description: "Direct assignment of a permission set to a user (optionally scoped to an organization).", + fields: { + id: { + label: "Assignment ID", + help: "UUID of the assignment." + }, + user_id: { + label: "User", + help: "Foreign key to sys_user." + }, + permission_set_id: { + label: "Permission Set", + help: "Foreign key to sys_permission_set." + }, + organization_id: { + label: "Organization", + help: "Optional organization scope. NULL = applies in every org context." + }, + granted_by: { + label: "Granted By", + help: "User who granted this permission set." + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + } + }, + sys_role_permission_set: { + label: "Role Permission Set", + pluralLabel: "Role Permission Sets", + description: "Binds a permission set to a role.", + fields: { + id: { + label: "Binding ID", + help: "UUID of the role-permission-set binding." + }, + role_id: { + label: "Role", + help: "Foreign key to sys_role." + }, + permission_set_id: { + label: "Permission Set", + help: "Foreign key to sys_permission_set." + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + } + } +}; diff --git a/packages/plugins/plugin-security/src/translations/es-ES.objects.generated.ts b/packages/plugins/plugin-security/src/translations/es-ES.objects.generated.ts new file mode 100644 index 000000000..61536b6df --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/es-ES.objects.generated.ts @@ -0,0 +1,216 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const esESObjects: NonNullable = { + sys_role: { + label: "Rol", + pluralLabel: "Roles", + description: "Definiciones de rol para el control de acceso RBAC", + fields: { + label: { + label: "Nombre visible" + }, + name: { + label: "Nombre de API", + help: "Nombre técnico único del rol (p. ej. admin, editor, viewer)." + }, + description: { + label: "Descripción" + }, + permissions: { + label: "Permisos", + help: "Matriz serializada en JSON de cadenas de permisos." + }, + active: { + label: "Activo" + }, + is_default: { + label: "Rol predeterminado", + help: "Se asigna automáticamente a los nuevos usuarios." + }, + id: { + label: "ID de rol" + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + }, + _views: { + active: { + label: "Activo" + }, + default_roles: { + label: "Predeterminado" + }, + custom: { + label: "Personalizado" + }, + all_roles: { + label: "Todos" + } + }, + _actions: { + activate_role: { + label: "Activar rol", + successMessage: "Rol activado" + }, + deactivate_role: { + label: "Desactivar rol", + confirmText: "¿Desactivar este rol? Los usuarios con el rol conservan su asignación, pero el rol deja de otorgar permisos hasta que se vuelva a activar.", + successMessage: "Rol desactivado" + }, + set_default_role: { + label: "Establecer como predeterminado", + confirmText: "¿Convertir este en el rol predeterminado para los nuevos usuarios? Los usuarios existentes no se ven afectados.", + successMessage: "Rol predeterminado actualizado" + }, + clone_role: { + label: "Clonar rol", + successMessage: "Rol clonado" + } + } + }, + sys_permission_set: { + label: "Conjunto de permisos", + pluralLabel: "Conjuntos de permisos", + description: "Agrupaciones de permisos con nombre para un control de acceso detallado", + fields: { + label: { + label: "Nombre visible" + }, + name: { + label: "Nombre de API", + help: "Nombre técnico único del conjunto de permisos." + }, + description: { + label: "Descripción" + }, + object_permissions: { + label: "Permisos de objeto", + help: "Permisos CRUD a nivel de objeto serializados en JSON." + }, + field_permissions: { + label: "Permisos de campo", + help: "Permisos de lectura/escritura a nivel de campo serializados en JSON." + }, + system_permissions: { + label: "Permisos del sistema", + help: "Array serializado en JSON de nombres de capacidades del sistema (p. ej. [\"setup.access\",\"studio.access\",\"manage_users\"])" + }, + row_level_security: { + label: "Seguridad a nivel de fila", + help: "Array serializado en JSON de políticas de seguridad a nivel de fila (cláusulas USING/CHECK)" + }, + tab_permissions: { + label: "Permisos de pestañas", + help: "Mapa serializado en JSON de la visibilidad de las pestañas de la app (visible | hidden | default_on | default_off)" + }, + active: { + label: "Activo" + }, + id: { + label: "ID de conjunto de permisos" + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + }, + _views: { + active: { + label: "Activo" + }, + inactive: { + label: "Inactivo" + }, + all_permsets: { + label: "Todos" + } + }, + _actions: { + activate_permission_set: { + label: "Activar", + successMessage: "Conjunto de permisos activado" + }, + deactivate_permission_set: { + label: "Desactivar", + confirmText: "¿Desactivar este conjunto de permisos? Las asignaciones existentes se mantienen, pero dejan de otorgar acceso hasta que se vuelva a activar.", + successMessage: "Conjunto de permisos desactivado" + }, + clone_permission_set: { + label: "Clonar", + successMessage: "Conjunto de permisos clonado" + } + } + }, + sys_user_permission_set: { + label: "Conjunto de permisos de usuario", + pluralLabel: "Conjuntos de permisos de usuario", + description: "Asignación directa de un conjunto de permisos a un usuario (opcionalmente con ámbito de organización).", + fields: { + id: { + label: "ID de asignación", + help: "UUID de la asignación." + }, + user_id: { + label: "Usuario", + help: "Clave foránea a sys_user." + }, + permission_set_id: { + label: "Conjunto de permisos", + help: "Clave foránea a sys_permission_set." + }, + organization_id: { + label: "Organización", + help: "Ámbito de organización opcional. NULL = se aplica en cualquier contexto de organización." + }, + granted_by: { + label: "Concedido por", + help: "Usuario que concedió este conjunto de permisos." + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + } + }, + sys_role_permission_set: { + label: "Conjunto de permisos de rol", + pluralLabel: "Conjuntos de permisos de rol", + description: "Vincula un conjunto de permisos a un rol.", + fields: { + id: { + label: "ID de vinculación", + help: "UUID de la vinculación rol-conjunto de permisos." + }, + role_id: { + label: "Rol", + help: "Clave foránea a sys_role." + }, + permission_set_id: { + label: "Conjunto de permisos", + help: "Clave foránea a sys_permission_set." + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + } + } +}; diff --git a/packages/plugins/plugin-security/src/translations/index.ts b/packages/plugins/plugin-security/src/translations/index.ts new file mode 100644 index 000000000..bbefc1b35 --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * SecurityTranslations — i18n bundle owned by this plugin (ADR-0029 D8). + * + * Object label/field/view/action translations for the sys_* objects this + * plugin owns. Loaded at runtime via the plugin's `kernel:ready` hook + * (`i18n.loadTranslations`). Regenerate with `os i18n extract` against + * `scripts/i18n-extract.config.ts`. + */ + +import type { TranslationBundle } from '@objectstack/spec/system'; +import { enObjects } from './en.objects.generated.js'; +import { zhCNObjects } from './zh-CN.objects.generated.js'; +import { jaJPObjects } from './ja-JP.objects.generated.js'; +import { esESObjects } from './es-ES.objects.generated.js'; + +export const SecurityTranslations: TranslationBundle = { + en: { objects: enObjects }, + 'zh-CN': { objects: zhCNObjects }, + 'ja-JP': { objects: jaJPObjects }, + 'es-ES': { objects: esESObjects }, +}; diff --git a/packages/plugins/plugin-security/src/translations/ja-JP.objects.generated.ts b/packages/plugins/plugin-security/src/translations/ja-JP.objects.generated.ts new file mode 100644 index 000000000..e905d970e --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/ja-JP.objects.generated.ts @@ -0,0 +1,216 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const jaJPObjects: NonNullable = { + sys_role: { + label: "ロール", + pluralLabel: "ロール", + description: "RBAC アクセス制御のためのロール定義", + fields: { + label: { + label: "表示名" + }, + name: { + label: "API 名", + help: "ロールの一意の機械名(例: admin、editor、viewer)" + }, + description: { + label: "説明" + }, + permissions: { + label: "権限", + help: "権限文字列の JSON シリアライズ配列" + }, + active: { + label: "有効" + }, + is_default: { + label: "デフォルトロール", + help: "新規ユーザーに自動的に割り当てられます" + }, + id: { + label: "ロール ID" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + }, + _views: { + active: { + label: "有効" + }, + default_roles: { + label: "デフォルト" + }, + custom: { + label: "カスタム" + }, + all_roles: { + label: "すべて" + } + }, + _actions: { + activate_role: { + label: "ロールを有効化", + successMessage: "ロールが有効化されました" + }, + deactivate_role: { + label: "ロールを無効化", + confirmText: "このロールを無効化しますか?このロールを持つユーザーの割り当ては維持されますが、再度有効化するまで権限の付与は停止されます。", + successMessage: "ロールが無効化されました" + }, + set_default_role: { + label: "デフォルトに設定", + confirmText: "このロールを新規ユーザーのデフォルトロールにしますか?既存のユーザーには影響しません。", + successMessage: "デフォルトロールを更新しました" + }, + clone_role: { + label: "ロールを複製", + successMessage: "ロールを複製しました" + } + } + }, + sys_permission_set: { + label: "権限セット", + pluralLabel: "権限セット", + description: "細かいアクセス制御のための権限グループ", + fields: { + label: { + label: "表示名" + }, + name: { + label: "API 名", + help: "権限セットの一意の機械名" + }, + description: { + label: "説明" + }, + object_permissions: { + label: "オブジェクト権限", + help: "JSON シリアライズされたオブジェクトレベルの CRUD 権限" + }, + field_permissions: { + label: "フィールド権限", + help: "JSON シリアライズされたフィールドレベルの読み取り/書き込み権限" + }, + system_permissions: { + label: "システム権限", + help: "システムケーパビリティ名のJSONシリアライズ配列(例: [\"setup.access\",\"studio.access\",\"manage_users\"])" + }, + row_level_security: { + label: "行レベルセキュリティ", + help: "行レベルセキュリティポリシーのJSONシリアライズ配列(USING/CHECK 句)" + }, + tab_permissions: { + label: "タブ権限", + help: "アプリのタブ表示のJSONシリアライズマップ(visible | hidden | default_on | default_off)" + }, + active: { + label: "有効" + }, + id: { + label: "権限セット ID" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + }, + _views: { + active: { + label: "有効" + }, + inactive: { + label: "無効" + }, + all_permsets: { + label: "すべて" + } + }, + _actions: { + activate_permission_set: { + label: "有効化", + successMessage: "権限セットが有効化されました" + }, + deactivate_permission_set: { + label: "無効化", + confirmText: "この権限セットを無効化しますか?既存の割り当ては維持されますが、再度有効化するまでアクセスの付与は停止されます。", + successMessage: "権限セットが無効化されました" + }, + clone_permission_set: { + label: "複製", + successMessage: "権限セットを複製しました" + } + } + }, + sys_user_permission_set: { + label: "ユーザー権限セット", + pluralLabel: "ユーザー権限セット", + description: "ユーザーへの権限セットの直接割り当て(組織スコープ可能)。", + fields: { + id: { + label: "割り当て ID", + help: "割り当ての UUID。" + }, + user_id: { + label: "ユーザー", + help: "sys_user への外部キー。" + }, + permission_set_id: { + label: "権限セット", + help: "sys_permission_set への外部キー。" + }, + organization_id: { + label: "組織", + help: "オプションの組織スコープ。NULL = すべての組織コンテキストで適用。" + }, + granted_by: { + label: "付与者", + help: "この権限セットを付与したユーザー。" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + } + }, + sys_role_permission_set: { + label: "ロール権限セット", + pluralLabel: "ロール権限セット", + description: "権限セットをロールにバインドします。", + fields: { + id: { + label: "バインド ID", + help: "ロール権限セットバインドの UUID。" + }, + role_id: { + label: "ロール", + help: "sys_role への外部キー。" + }, + permission_set_id: { + label: "権限セット", + help: "sys_permission_set への外部キー。" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + } + } +}; diff --git a/packages/plugins/plugin-security/src/translations/zh-CN.objects.generated.ts b/packages/plugins/plugin-security/src/translations/zh-CN.objects.generated.ts new file mode 100644 index 000000000..d27ef37e6 --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/zh-CN.objects.generated.ts @@ -0,0 +1,216 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const zhCNObjects: NonNullable = { + sys_role: { + label: "角色", + pluralLabel: "角色", + description: "用于 RBAC 访问控制的角色定义", + fields: { + label: { + label: "显示名称" + }, + name: { + label: "API 名称", + help: "角色的唯一机器名称(例如 admin、editor、viewer)" + }, + description: { + label: "描述" + }, + permissions: { + label: "权限", + help: "权限字符串数组的 JSON 序列化内容" + }, + active: { + label: "启用" + }, + is_default: { + label: "默认角色", + help: "自动分配给新用户" + }, + id: { + label: "角色 ID" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + }, + _views: { + active: { + label: "启用" + }, + default_roles: { + label: "默认" + }, + custom: { + label: "自定义" + }, + all_roles: { + label: "全部" + } + }, + _actions: { + activate_role: { + label: "激活角色", + successMessage: "角色已激活" + }, + deactivate_role: { + label: "停用角色", + confirmText: "确定要停用此角色吗?拥有该角色的用户仍保留其分配,但在重新激活之前该角色将不再授予权限。", + successMessage: "角色已停用" + }, + set_default_role: { + label: "设为默认", + confirmText: "将此角色设为新用户的默认角色吗?现有用户不受影响。", + successMessage: "已更新默认角色" + }, + clone_role: { + label: "克隆角色", + successMessage: "已克隆角色" + } + } + }, + sys_permission_set: { + label: "权限集", + pluralLabel: "权限集", + description: "用于精细化访问控制的命名权限分组", + fields: { + label: { + label: "显示名称" + }, + name: { + label: "API 名称", + help: "权限集的唯一机器名称" + }, + description: { + label: "描述" + }, + object_permissions: { + label: "对象权限", + help: "对象级 CRUD 权限的 JSON 序列化内容" + }, + field_permissions: { + label: "字段权限", + help: "字段级读写权限的 JSON 序列化内容" + }, + system_permissions: { + label: "系统权限", + help: "系统能力名称的 JSON 序列化数组(例如 [\"setup.access\",\"studio.access\",\"manage_users\"])" + }, + row_level_security: { + label: "行级安全", + help: "行级安全策略的 JSON 序列化数组(USING/CHECK 子句)" + }, + tab_permissions: { + label: "标签页权限", + help: "应用标签页可见性的 JSON 序列化映射(visible | hidden | default_on | default_off)" + }, + active: { + label: "启用" + }, + id: { + label: "权限集 ID" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + }, + _views: { + active: { + label: "启用" + }, + inactive: { + label: "停用" + }, + all_permsets: { + label: "全部" + } + }, + _actions: { + activate_permission_set: { + label: "激活", + successMessage: "权限集已激活" + }, + deactivate_permission_set: { + label: "停用", + confirmText: "确定要停用此权限集吗?现有分配仍将保留,但在重新激活之前将不再授予访问权限。", + successMessage: "权限集已停用" + }, + clone_permission_set: { + label: "克隆", + successMessage: "已克隆权限集" + } + } + }, + sys_user_permission_set: { + label: "用户权限集", + pluralLabel: "用户权限集", + description: "将权限集直接分配给用户(可按组织范围限定)。", + fields: { + id: { + label: "分配 ID", + help: "该分配记录的 UUID。" + }, + user_id: { + label: "用户", + help: "指向 sys_user 的外键。" + }, + permission_set_id: { + label: "权限集", + help: "指向 sys_permission_set 的外键。" + }, + organization_id: { + label: "组织", + help: "可选的组织范围。NULL = 在所有组织上下文中都生效。" + }, + granted_by: { + label: "授权人", + help: "授予该权限集的用户。" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + } + }, + sys_role_permission_set: { + label: "角色权限集", + pluralLabel: "角色权限集", + description: "将权限集绑定到角色。", + fields: { + id: { + label: "绑定 ID", + help: "角色-权限集绑定记录的 UUID。" + }, + role_id: { + label: "角色", + help: "指向 sys_role 的外键。" + }, + permission_set_id: { + label: "权限集", + help: "指向 sys_permission_set 的外键。" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts new file mode 100644 index 000000000..7c3e56cc1 --- /dev/null +++ b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Build-time only config for `os i18n extract` (ADR-0029 D8). Not deployed. + * The plugin owns the i18n extraction for the objects it owns; the + * `translations` baseline is this plugin's OWN generated bundles so re-running + * `--merge` preserves every hand-translated string. (Initial zh-CN/ja-JP/es-ES + * strings were seeded from @objectstack/platform-objects.) + * + * os i18n extract packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts \ + * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only \ + * --out=packages/plugins/plugin-sharing/src/translations + */ + +import { defineStack } from '@objectstack/spec'; +import { SysRecordShare, SysSharingRule, SysShareLink } from '../src/objects/index.js'; +import { enObjects } from '../src/translations/en.objects.generated.js'; +import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js'; +import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js'; +import { esESObjects } from '../src/translations/es-ES.objects.generated.js'; + +export default defineStack({ + name: 'plugin-sharing-i18n-extract', + objects: [SysRecordShare, SysSharingRule, SysShareLink] as any, + translations: [ + { en: { objects: enObjects } }, + { 'zh-CN': { objects: zhCNObjects } }, + { 'ja-JP': { objects: jaJPObjects } }, + { 'es-ES': { objects: esESObjects } }, + ], +}); diff --git a/packages/plugins/plugin-sharing/src/sharing-plugin.ts b/packages/plugins/plugin-sharing/src/sharing-plugin.ts index fda9c417e..61c2d2db1 100644 --- a/packages/plugins/plugin-sharing/src/sharing-plugin.ts +++ b/packages/plugins/plugin-sharing/src/sharing-plugin.ts @@ -104,6 +104,22 @@ export class SharingServicePlugin implements Plugin { }, ], }); + + // ADR-0029 D8 — contribute this plugin's object translations to the i18n + // service on kernel:ready (the i18n plugin may register after this one). + if (typeof (ctx as any).hook === 'function') { + (ctx as any).hook('kernel:ready', async () => { + try { + const i18n = ctx.getService('i18n'); + if (i18n && typeof i18n.loadTranslations === 'function') { + const { SharingTranslations } = await import('./translations/index.js'); + for (const [locale, data] of Object.entries(SharingTranslations)) { + i18n.loadTranslations(locale, data as Record); + } + } + } catch { /* i18n optional */ } + }); + } ctx.logger.info('SharingServicePlugin: schema registered'); } diff --git a/packages/plugins/plugin-sharing/src/translations/en.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/en.objects.generated.ts new file mode 100644 index 000000000..dfab4c2a6 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/en.objects.generated.ts @@ -0,0 +1,275 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'en'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const enObjects: NonNullable = { + sys_record_share: { + label: "Record Share", + pluralLabel: "Record Shares", + description: "Per-record sharing grant — extends OWD with explicit access", + fields: { + id: { + label: "Share ID" + }, + object_name: { + label: "Object", + help: "Short object name of the shared record" + }, + record_id: { + label: "Record", + help: "Primary key of the shared record within object_name" + }, + recipient_type: { + label: "Recipient Type", + help: "Kind of principal that holds the grant", + options: { + user: "user", + group: "group", + role: "role", + role_and_subordinates: "role_and_subordinates", + guest: "guest" + } + }, + recipient_id: { + label: "Recipient", + help: "ID of the user/group/role that receives access" + }, + access_level: { + label: "Access Level", + help: "What the recipient can do — read | edit | full (transfer/share/delete)", + options: { + read: "read", + edit: "edit", + full: "full" + } + }, + source: { + label: "Source", + help: "Why this grant exists — used by the rule evaluator to reconcile", + options: { + manual: "manual", + rule: "rule", + team: "team", + inherited: "inherited" + } + }, + source_id: { + label: "Source ID", + help: "Rule name / team id when source != manual" + }, + granted_by: { + label: "Granted By", + help: "User that created the grant (manual only)" + }, + reason: { + label: "Reason", + help: "Optional free-text explanation surfaced to the recipient" + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + }, + _views: { + granted_to_me: { + label: "Granted to Me" + }, + granted_by_me: { + label: "Granted by Me" + }, + by_object: { + label: "By Object" + }, + manual_grants: { + label: "Manual Grants" + }, + rule_grants: { + label: "Rule Grants" + }, + all_shares: { + label: "All" + } + } + }, + sys_sharing_rule: { + label: "Sharing Rule", + pluralLabel: "Sharing Rules", + description: "Declarative sharing rule that auto-materialises sys_record_share grants. Authored via defineSharingRule() in code or the Studio criteria builder.", + fields: { + id: { + label: "Rule ID" + }, + organization_id: { + label: "Organization", + help: "Tenant that owns this rule; null = global" + }, + name: { + label: "Name", + help: "Unique snake_case rule name" + }, + label: { + label: "Display Label" + }, + description: { + label: "Description" + }, + object_name: { + label: "Object", + help: "Short object name (e.g. opportunity, account)" + }, + criteria_json: { + label: "Criteria (FilterCondition JSON)", + help: "JSON FilterCondition matched against records of object_name. Empty = match all." + }, + recipient_type: { + label: "Recipient Type", + help: "Kind of principal that receives access — expanded to user grants at evaluation time. `department` walks the parent_department_id tree; `team` is flat (better-auth).", + options: { + user: "user", + team: "team", + department: "department", + role: "role", + queue: "queue" + } + }, + recipient_id: { + label: "Recipient", + help: "department id / team id / role name / queue name / user id depending on recipient_type" + }, + access_level: { + label: "Access Level", + options: { + read: "read", + edit: "edit", + full: "full" + } + }, + active: { + label: "Active", + help: "Only active rules participate in lifecycle evaluation" + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + }, + _views: { + active: { + label: "Active" + }, + inactive: { + label: "Inactive" + }, + by_object: { + label: "By Object" + }, + all_rules: { + label: "All" + } + } + }, + sys_share_link: { + label: "Share Link", + pluralLabel: "Share Links", + description: "Opaque capability token granting access to a single record. Notion/Figma-style public link sharing.", + fields: { + id: { + label: "Link ID" + }, + token: { + label: "Token", + help: "Opaque URL-safe random token (≥ 22 chars). The only secret in this row." + }, + object_name: { + label: "Object", + help: "Short object name of the shared record (e.g. ai_conversation, contracts_contract)" + }, + record_id: { + label: "Record", + help: "Primary key of the shared record within object_name" + }, + permission: { + label: "Permission", + help: "What the link holder can do with the record", + options: { + view: "View", + comment: "Comment", + edit: "Edit" + } + }, + audience: { + label: "Audience", + help: "Gating layer applied on top of the token check", + options: { + public: "Public (indexable)", + link_only: "Anyone with the link", + signed_in: "Signed-in users", + email: "Specific emails" + } + }, + expires_at: { + label: "Expires At", + help: "When set, resolveToken returns null after this timestamp" + }, + email_allowlist: { + label: "Email Allowlist", + help: "Lowercased addresses checked when audience=email" + }, + password_hash: { + label: "Password Hash", + help: "Argon2/bcrypt hash. When set, the UI prompts for a password before rendering." + }, + redact_fields: { + label: "Per-Link Redactions", + help: "Extra fields stripped from the response, on top of the object-default set" + }, + label: { + label: "Label", + help: "Free-text shown in the share dialog (e.g. \"ACME Q3 contract\")" + }, + revoked_at: { + label: "Revoked At", + help: "When set, the link is permanently disabled" + }, + created_by: { + label: "Created By", + help: "Issuer of the link" + }, + created_at: { + label: "Created At" + }, + last_used_at: { + label: "Last Used At", + help: "Stamped by resolveToken; used by the dashboard to highlight active links" + }, + use_count: { + label: "Use Count", + help: "Incremented by resolveToken on every successful resolution" + } + }, + _views: { + active_links: { + label: "Active" + }, + by_me: { + label: "Created by Me" + }, + revoked: { + label: "Revoked" + }, + all_links: { + label: "All" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/es-ES.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/es-ES.objects.generated.ts new file mode 100644 index 000000000..3c63418ea --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/es-ES.objects.generated.ts @@ -0,0 +1,275 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const esESObjects: NonNullable = { + sys_record_share: { + label: "Compartición de registro", + pluralLabel: "Comparticiones de registro", + description: "Concesión de compartición por registro; amplía OWD con acceso explícito.", + fields: { + id: { + label: "ID de compartición" + }, + object_name: { + label: "Objeto", + help: "Nombre corto del objeto del registro compartido." + }, + record_id: { + label: "Registro", + help: "Clave primaria del registro compartido dentro de object_name." + }, + recipient_type: { + label: "Tipo de destinatario", + help: "Tipo de principal que posee la concesión.", + options: { + user: "Usuario", + group: "Grupo", + role: "Rol", + role_and_subordinates: "Rol y subordinados", + guest: "Invitado" + } + }, + recipient_id: { + label: "Destinatario", + help: "ID del usuario/grupo/rol que recibe acceso." + }, + access_level: { + label: "Nivel de acceso", + help: "Lo que puede hacer el destinatario: read | edit | full (transfer/share/delete).", + options: { + read: "Leer", + edit: "Editar", + full: "Total" + } + }, + source: { + label: "Origen", + help: "Motivo por el que existe esta concesión; lo utiliza el evaluador de reglas para reconciliar.", + options: { + manual: "Manual", + rule: "Regla", + team: "Equipo", + inherited: "Heredado" + } + }, + source_id: { + label: "ID de origen", + help: "Nombre de la regla / ID del equipo cuando source != manual." + }, + granted_by: { + label: "Concedido por", + help: "Usuario que creó la concesión (solo manual)." + }, + reason: { + label: "Motivo", + help: "Explicación opcional en texto libre que se muestra al destinatario." + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + }, + _views: { + granted_to_me: { + label: "Concedidos a mí" + }, + granted_by_me: { + label: "Concedidos por mí" + }, + by_object: { + label: "Por objeto" + }, + manual_grants: { + label: "Concesiones manuales" + }, + rule_grants: { + label: "Concesiones por regla" + }, + all_shares: { + label: "Todas" + } + } + }, + sys_sharing_rule: { + label: "Regla de compartición", + pluralLabel: "Reglas de compartición", + description: "Regla de compartición declarativa que materializa automáticamente concesiones de sys_record_share. Se define mediante defineSharingRule() en código o con el generador de criterios de Studio.", + fields: { + id: { + label: "ID de regla" + }, + organization_id: { + label: "Organización", + help: "Tenant que posee esta regla; null = global." + }, + name: { + label: "Nombre", + help: "Nombre de regla snake_case único." + }, + label: { + label: "Nombre visible" + }, + description: { + label: "Descripción" + }, + object_name: { + label: "Objeto", + help: "Nombre corto del objeto (p. ej. opportunity, account)." + }, + criteria_json: { + label: "Criterios (JSON de FilterCondition)", + help: "FilterCondition JSON comparado con los registros de object_name. Vacío = coincide con todos." + }, + recipient_type: { + label: "Tipo de destinatario", + help: "Tipo de principal que recibe acceso; se expande a concesiones de usuario durante la evaluación. `department` recorre el árbol parent_department_id; `team` es plano (better-auth).", + options: { + user: "Usuario", + team: "Equipo", + department: "Departamento", + role: "Rol", + queue: "Cola" + } + }, + recipient_id: { + label: "Destinatario", + help: "ID de departamento / ID de equipo / nombre del rol / nombre de la cola / ID del usuario según recipient_type." + }, + access_level: { + label: "Nivel de acceso", + options: { + read: "Leer", + edit: "Editar", + full: "Total" + } + }, + active: { + label: "Activo", + help: "Solo las reglas activas participan en la evaluación del ciclo de vida." + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + }, + _views: { + active: { + label: "Activo" + }, + inactive: { + label: "Inactivo" + }, + by_object: { + label: "Por objeto" + }, + all_rules: { + label: "Todas" + } + } + }, + sys_share_link: { + label: "Enlace de uso compartido", + pluralLabel: "Enlaces de uso compartido", + description: "Token de capacidad opaco que concede acceso a un único registro. Uso compartido mediante enlace público al estilo de Notion/Figma.", + fields: { + id: { + label: "ID del enlace" + }, + token: { + label: "Token", + help: "Token aleatorio opaco seguro para URL (≥ 22 caracteres). El único secreto de esta fila." + }, + object_name: { + label: "Objeto", + help: "Nombre corto del objeto del registro compartido (p. ej. ai_conversation, contracts_contract)" + }, + record_id: { + label: "Registro", + help: "Clave principal del registro compartido dentro de object_name" + }, + permission: { + label: "Permiso", + help: "Lo que el titular del enlace puede hacer con el registro", + options: { + view: "Ver", + comment: "Comentar", + edit: "Editar" + } + }, + audience: { + label: "Audiencia", + help: "Capa de control aplicada por encima de la verificación del token", + options: { + public: "Público (indexable)", + link_only: "Cualquier persona con el enlace", + signed_in: "Usuarios con sesión iniciada", + email: "Correos específicos" + } + }, + expires_at: { + label: "Caduca el", + help: "Cuando se establece, resolveToken devuelve null después de esta marca de tiempo" + }, + email_allowlist: { + label: "Lista de correos permitidos", + help: "Direcciones en minúsculas que se comprueban cuando audience=email" + }, + password_hash: { + label: "Hash de contraseña", + help: "Hash Argon2/bcrypt. Cuando se establece, la interfaz solicita una contraseña antes de mostrar el contenido." + }, + redact_fields: { + label: "Campos ocultos por enlace", + help: "Campos adicionales que se eliminan de la respuesta, además del conjunto predeterminado del objeto" + }, + label: { + label: "Etiqueta", + help: "Texto libre que se muestra en el cuadro de diálogo de uso compartido (p. ej. \"ACME Q3 contract\")" + }, + revoked_at: { + label: "Revocado el", + help: "Cuando se establece, el enlace queda deshabilitado permanentemente" + }, + created_by: { + label: "Creado por", + help: "Emisor del enlace" + }, + created_at: { + label: "Creado el" + }, + last_used_at: { + label: "Último uso el", + help: "Lo registra resolveToken; el panel lo usa para resaltar los enlaces activos" + }, + use_count: { + label: "Número de usos", + help: "Lo incrementa resolveToken en cada resolución correcta" + } + }, + _views: { + active_links: { + label: "Activos" + }, + by_me: { + label: "Creados por mí" + }, + revoked: { + label: "Revocados" + }, + all_links: { + label: "Todos" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/index.ts b/packages/plugins/plugin-sharing/src/translations/index.ts new file mode 100644 index 000000000..7aea2a465 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * SharingTranslations — i18n bundle owned by this plugin (ADR-0029 D8). + * + * Object label/field/view/action translations for the sys_* objects this + * plugin owns. Loaded at runtime via the plugin's `kernel:ready` hook + * (`i18n.loadTranslations`). Regenerate with `os i18n extract` against + * `scripts/i18n-extract.config.ts`. + */ + +import type { TranslationBundle } from '@objectstack/spec/system'; +import { enObjects } from './en.objects.generated.js'; +import { zhCNObjects } from './zh-CN.objects.generated.js'; +import { jaJPObjects } from './ja-JP.objects.generated.js'; +import { esESObjects } from './es-ES.objects.generated.js'; + +export const SharingTranslations: TranslationBundle = { + en: { objects: enObjects }, + 'zh-CN': { objects: zhCNObjects }, + 'ja-JP': { objects: jaJPObjects }, + 'es-ES': { objects: esESObjects }, +}; diff --git a/packages/plugins/plugin-sharing/src/translations/ja-JP.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/ja-JP.objects.generated.ts new file mode 100644 index 000000000..6320ccc61 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ja-JP.objects.generated.ts @@ -0,0 +1,275 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const jaJPObjects: NonNullable = { + sys_record_share: { + label: "レコード共有", + pluralLabel: "レコード共有", + description: "レコードごとの共有付与 — OWD に明示的なアクセスを追加", + fields: { + id: { + label: "共有 ID" + }, + object_name: { + label: "オブジェクト", + help: "共有レコードの短いオブジェクト名" + }, + record_id: { + label: "レコード", + help: "object_name 内の共有レコードの主キー" + }, + recipient_type: { + label: "受信者タイプ", + help: "付与を保持するプリンシパルの種別", + options: { + user: "ユーザー", + group: "グループ", + role: "ロール", + role_and_subordinates: "ロールと下位階層", + guest: "ゲスト" + } + }, + recipient_id: { + label: "受信者", + help: "アクセスを受け取るユーザー/グループ/ロールの ID" + }, + access_level: { + label: "アクセスレベル", + help: "受信者に許可される操作 — read | edit | full(転送/共有/削除)", + options: { + read: "閲覧", + edit: "編集", + full: "フルアクセス" + } + }, + source: { + label: "ソース", + help: "この付与が存在する理由 — ルール評価者が調整に使用", + options: { + manual: "手動", + rule: "ルール", + team: "チーム", + inherited: "継承" + } + }, + source_id: { + label: "ソース ID", + help: "source != manual の場合のルール名 / チーム ID" + }, + granted_by: { + label: "付与者", + help: "付与を作成したユーザー(手動のみ)" + }, + reason: { + label: "理由", + help: "受信者に表示されるオプションの自由記述説明" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + }, + _views: { + granted_to_me: { + label: "自分への付与" + }, + granted_by_me: { + label: "自分による付与" + }, + by_object: { + label: "オブジェクト別" + }, + manual_grants: { + label: "手動付与" + }, + rule_grants: { + label: "ルール付与" + }, + all_shares: { + label: "すべて" + } + } + }, + sys_sharing_rule: { + label: "共有ルール", + pluralLabel: "共有ルール", + description: "sys_record_share 付与を自動マテリアライズする宣言的共有ルール。コードの defineSharingRule() または Studio の条件ビルダーで作成します。", + fields: { + id: { + label: "ルール ID" + }, + organization_id: { + label: "組織", + help: "このルールを所有するテナント。null = グローバル" + }, + name: { + label: "名前", + help: "一意の snake_case ルール名" + }, + label: { + label: "表示名" + }, + description: { + label: "説明" + }, + object_name: { + label: "オブジェクト", + help: "短いオブジェクト名(例: opportunity、account)" + }, + criteria_json: { + label: "条件(FilterCondition JSON)", + help: "object_name のレコードに対してマッチする JSON FilterCondition。空 = すべてにマッチ。" + }, + recipient_type: { + label: "受信者タイプ", + help: "アクセスを受け取るプリンシパルの種別 — 評価時にユーザー付与に展開されます。`department` は parent_department_id ツリーをたどります。`team` はフラット(better-auth)。", + options: { + user: "ユーザー", + team: "チーム", + department: "部門", + role: "ロール", + queue: "キュー" + } + }, + recipient_id: { + label: "受信者", + help: "recipient_type に応じた部門 ID / チーム ID / ロール名 / キュー名 / ユーザー ID" + }, + access_level: { + label: "アクセスレベル", + options: { + read: "閲覧", + edit: "編集", + full: "フルアクセス" + } + }, + active: { + label: "有効", + help: "有効なルールのみがライフサイクル評価に参加します" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + }, + _views: { + active: { + label: "有効" + }, + inactive: { + label: "無効" + }, + by_object: { + label: "オブジェクト別" + }, + all_rules: { + label: "すべて" + } + } + }, + sys_share_link: { + label: "共有リンク", + pluralLabel: "共有リンク", + description: "単一レコードへのアクセスを許可する不透明なケーパビリティトークン。Notion / Figma スタイルの公開リンク共有。", + fields: { + id: { + label: "リンク ID" + }, + token: { + label: "トークン", + help: "URL セーフな不透明ランダムトークン(22 文字以上)。この行で唯一の機密情報です。" + }, + object_name: { + label: "オブジェクト", + help: "共有対象レコードのオブジェクト短縮名(例: ai_conversation、contracts_contract)" + }, + record_id: { + label: "レコード", + help: "object_name 内における共有対象レコードの主キー" + }, + permission: { + label: "権限", + help: "リンク保持者がレコードに対して実行できる操作", + options: { + view: "閲覧", + comment: "コメント", + edit: "編集" + } + }, + audience: { + label: "対象者", + help: "トークン検証の上に適用されるアクセス制御レイヤー", + options: { + public: "公開(インデックス可能)", + link_only: "リンクを知っている全員", + signed_in: "サインイン済みユーザー", + email: "特定のメールアドレス" + } + }, + expires_at: { + label: "有効期限", + help: "設定すると、このタイムスタンプ以降は resolveToken が null を返します" + }, + email_allowlist: { + label: "メール許可リスト", + help: "audience=email のときに照合される小文字のメールアドレス" + }, + password_hash: { + label: "パスワードハッシュ", + help: "Argon2/bcrypt ハッシュ。設定すると、表示前に UI がパスワードの入力を求めます。" + }, + redact_fields: { + label: "リンク単位のマスキング", + help: "オブジェクト既定のマスキング集合に加えて、レスポンスから除外する追加フィールド" + }, + label: { + label: "ラベル", + help: "共有ダイアログに表示される自由記述テキスト(例: \"ACME Q3 contract\")" + }, + revoked_at: { + label: "失効日時", + help: "設定すると、リンクは恒久的に無効化されます" + }, + created_by: { + label: "作成者", + help: "リンクの発行者" + }, + created_at: { + label: "作成日時" + }, + last_used_at: { + label: "最終使用日時", + help: "resolveToken によって記録されます。ダッシュボードでアクティブなリンクを強調表示するために使用されます" + }, + use_count: { + label: "使用回数", + help: "解決が成功するたびに resolveToken によって加算されます" + } + }, + _views: { + active_links: { + label: "アクティブ" + }, + by_me: { + label: "自分が作成" + }, + revoked: { + label: "失効済み" + }, + all_links: { + label: "すべて" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/zh-CN.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/zh-CN.objects.generated.ts new file mode 100644 index 000000000..5b2658334 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/zh-CN.objects.generated.ts @@ -0,0 +1,275 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const zhCNObjects: NonNullable = { + sys_record_share: { + label: "记录共享", + pluralLabel: "记录共享", + description: "按记录粒度的共享授权——在 OWD 基础上提供显式访问", + fields: { + id: { + label: "共享 ID" + }, + object_name: { + label: "对象", + help: "被共享记录的短对象名" + }, + record_id: { + label: "记录", + help: "object_name 对应对象内该共享记录的主键" + }, + recipient_type: { + label: "接收方类型", + help: "持有该授权的主体类型", + options: { + user: "用户", + group: "组", + role: "角色", + role_and_subordinates: "角色及下级", + guest: "访客" + } + }, + recipient_id: { + label: "接收方", + help: "获得访问权限的用户 / 组 / 角色 ID" + }, + access_level: { + label: "访问级别", + help: "接收方可以执行的操作——read | edit | full(转移 / 共享 / 删除)", + options: { + read: "读取", + edit: "编辑", + full: "完全访问" + } + }, + source: { + label: "来源", + help: "该授权存在的原因——供规则求值器对账使用", + options: { + manual: "手动", + rule: "规则", + team: "团队", + inherited: "继承" + } + }, + source_id: { + label: "来源 ID", + help: "当 source != manual 时,对应规则名 / 团队 ID" + }, + granted_by: { + label: "授权人", + help: "创建该授权的用户(仅手动授权)" + }, + reason: { + label: "原因", + help: "可选的自由文本说明,会展示给接收方" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + }, + _views: { + granted_to_me: { + label: "授予我的" + }, + granted_by_me: { + label: "我授予的" + }, + by_object: { + label: "按对象" + }, + manual_grants: { + label: "手动授权" + }, + rule_grants: { + label: "规则授权" + }, + all_shares: { + label: "全部" + } + } + }, + sys_sharing_rule: { + label: "共享规则", + pluralLabel: "共享规则", + description: "声明式共享规则,会自动生成 sys_record_share 授权。可在代码中通过 defineSharingRule() 编写,或在 Studio 条件构建器中维护。", + fields: { + id: { + label: "规则 ID" + }, + organization_id: { + label: "组织", + help: "拥有该规则的租户;null = 全局" + }, + name: { + label: "名称", + help: "唯一的 snake_case 规则名称" + }, + label: { + label: "显示标签" + }, + description: { + label: "描述" + }, + object_name: { + label: "对象", + help: "短对象名(例如 opportunity、account)" + }, + criteria_json: { + label: "条件(FilterCondition JSON)", + help: "针对 object_name 记录匹配的 JSON FilterCondition。为空表示匹配全部。" + }, + recipient_type: { + label: "接收方类型", + help: "接收访问权限的主体类型——求值时会展开为用户授权。`department` 会沿 parent_department_id 树展开;`team` 为扁平结构(better-auth)。", + options: { + user: "用户", + team: "团队", + department: "部门", + role: "角色", + queue: "队列" + } + }, + recipient_id: { + label: "接收方", + help: "根据 recipient_type 填写 department id / team id / role name / queue name / user id" + }, + access_level: { + label: "访问级别", + options: { + read: "读取", + edit: "编辑", + full: "完全访问" + } + }, + active: { + label: "启用", + help: "只有启用的规则才会参与生命周期求值" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + }, + _views: { + active: { + label: "启用" + }, + inactive: { + label: "停用" + }, + by_object: { + label: "按对象" + }, + all_rules: { + label: "全部" + } + } + }, + sys_share_link: { + label: "共享链接", + pluralLabel: "共享链接", + description: "授予对单条记录访问权限的不透明能力令牌。类似 Notion/Figma 的公开链接共享。", + fields: { + id: { + label: "链接 ID" + }, + token: { + label: "令牌", + help: "URL 安全的不透明随机令牌(≥ 22 个字符)。本记录中唯一的机密信息。" + }, + object_name: { + label: "对象", + help: "所共享记录的对象短名称(例如 ai_conversation、contracts_contract)" + }, + record_id: { + label: "记录", + help: "object_name 内所共享记录的主键" + }, + permission: { + label: "权限", + help: "链接持有者可对该记录执行的操作", + options: { + view: "查看", + comment: "评论", + edit: "编辑" + } + }, + audience: { + label: "受众", + help: "在令牌校验之上额外施加的访问限制层", + options: { + public: "公开(可被索引)", + link_only: "任何持有链接的人", + signed_in: "已登录用户", + email: "指定邮箱" + } + }, + expires_at: { + label: "过期时间", + help: "设置后,超过此时间点 resolveToken 将返回 null" + }, + email_allowlist: { + label: "邮箱白名单", + help: "当 audience=email 时校验的小写邮箱地址" + }, + password_hash: { + label: "密码哈希", + help: "Argon2/bcrypt 哈希值。设置后,界面会在呈现内容前提示输入密码。" + }, + redact_fields: { + label: "按链接脱敏字段", + help: "在对象默认脱敏集之上,从响应中额外剔除的字段" + }, + label: { + label: "标签", + help: "在共享对话框中显示的自由文本(例如 \"ACME Q3 合同\")" + }, + revoked_at: { + label: "撤销时间", + help: "设置后,该链接将被永久停用" + }, + created_by: { + label: "创建人", + help: "链接的签发者" + }, + created_at: { + label: "创建时间" + }, + last_used_at: { + label: "最近使用时间", + help: "由 resolveToken 标记;仪表盘据此高亮显示活跃链接" + }, + use_count: { + label: "使用次数", + help: "每次成功解析时由 resolveToken 递增" + } + }, + _views: { + active_links: { + label: "活跃" + }, + by_me: { + label: "我创建的" + }, + revoked: { + label: "已撤销" + }, + all_links: { + label: "全部" + } + } + } +}; diff --git a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts new file mode 100644 index 000000000..354dc7ea1 --- /dev/null +++ b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Build-time only config for `os i18n extract` (ADR-0029 D8). Not deployed. + * The plugin owns the i18n extraction for the objects it owns; the + * `translations` baseline is this plugin's OWN generated bundles so re-running + * `--merge` preserves every hand-translated string. (Initial zh-CN/ja-JP/es-ES + * strings were seeded from @objectstack/platform-objects.) + * + * os i18n extract packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts \ + * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only \ + * --out=packages/plugins/plugin-webhooks/src/translations + */ + +import { defineStack } from '@objectstack/spec'; +import { SysWebhook } from '../src/sys-webhook.object.js'; +import { SysWebhookDelivery } from '../src/sys-webhook-delivery.object.js'; +import { enObjects } from '../src/translations/en.objects.generated.js'; +import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js'; +import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js'; +import { esESObjects } from '../src/translations/es-ES.objects.generated.js'; + +export default defineStack({ + name: 'plugin-webhooks-i18n-extract', + objects: [SysWebhook, SysWebhookDelivery] as any, + translations: [ + { en: { objects: enObjects } }, + { 'zh-CN': { objects: zhCNObjects } }, + { 'ja-JP': { objects: jaJPObjects } }, + { 'es-ES': { objects: esESObjects } }, + ], +}); diff --git a/packages/plugins/plugin-webhooks/src/translations/en.objects.generated.ts b/packages/plugins/plugin-webhooks/src/translations/en.objects.generated.ts new file mode 100644 index 000000000..319d903db --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/en.objects.generated.ts @@ -0,0 +1,187 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'en'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const enObjects: NonNullable = { + sys_webhook: { + label: "Webhook", + pluralLabel: "Webhooks", + description: "Outbound HTTP webhook subscription. Authored via defineWebhook() in code or the Studio editor; executed by the HTTP connector plugin.", + fields: { + id: { + label: "Webhook ID" + }, + name: { + label: "Name", + help: "Unique snake_case name — referenced in logs and audit" + }, + label: { + label: "Display Label" + }, + object_name: { + label: "Object", + help: "Short object name whose events fire this webhook (blank = manual / API-triggered)" + }, + triggers: { + label: "Triggers", + help: "Comma-separated event list: create,update,delete,undelete,api" + }, + url: { + label: "Target URL", + help: "External endpoint that receives the POST" + }, + method: { + label: "HTTP Method", + help: "GET / POST / PUT / PATCH / DELETE" + }, + description: { + label: "Description" + }, + active: { + label: "Active", + help: "Inactive webhooks are skipped by the dispatcher" + }, + definition_json: { + label: "Definition", + help: "Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — full headers/auth/retry/payload config" + }, + created_at: { + label: "Created At" + }, + updated_at: { + label: "Updated At" + } + }, + _views: { + active: { + label: "Active" + }, + inactive: { + label: "Inactive" + }, + by_object: { + label: "By Object" + }, + all_webhooks: { + label: "All" + } + } + }, + sys_webhook_delivery: { + label: "Webhook Delivery", + pluralLabel: "Webhook Deliveries", + description: "Durable outbox row for one webhook attempt. Managed by @objectstack/plugin-webhooks; do not write directly.", + fields: { + id: { + label: "Delivery ID", + help: "UUID — also doubles as the receiver-side idempotency key" + }, + webhook_id: { + label: "Webhook ID", + help: "FK to sys_webhook.id (loosely coupled — denormalised URL/secret on row)" + }, + event_id: { + label: "Event ID", + help: "Source event id; UNIQUE(event_id, webhook_id) for dedup" + }, + event_type: { + label: "Event Type", + help: "e.g. data.record.created" + }, + url: { + label: "Target URL", + help: "Snapshotted at enqueue so config edits do not rewrite live rows" + }, + method: { + label: "Method" + }, + headers_json: { + label: "Headers JSON" + }, + secret: { + label: "HMAC Secret" + }, + timeout_ms: { + label: "Timeout (ms)" + }, + payload_json: { + label: "Payload JSON" + }, + partition_key: { + label: "Partition", + help: "hash(webhook_id) mod partitionCount — precomputed for cheap WHERE" + }, + status: { + label: "Status", + help: "pending | in_flight | success | failed | dead" + }, + attempts: { + label: "Attempts", + help: "Number of POST attempts made so far" + }, + claimed_by: { + label: "Claimed By" + }, + claimed_at: { + label: "Claimed At (ms)" + }, + next_retry_at: { + label: "Next Retry At (ms)" + }, + last_attempted_at: { + label: "Last Attempted At (ms)" + }, + response_code: { + label: "HTTP Status" + }, + response_body: { + label: "Response Body (capped)" + }, + error: { + label: "Error" + }, + created_at: { + label: "Created At (ms)" + }, + updated_at: { + label: "Updated At (ms)" + } + }, + _views: { + recent: { + label: "Recent" + }, + failures: { + label: "Failures" + }, + in_flight: { + label: "In Flight" + }, + pending: { + label: "Pending" + }, + by_status: { + label: "By Status" + }, + by_webhook: { + label: "By Webhook" + }, + all_deliveries: { + label: "All" + } + }, + _actions: { + redeliver: { + label: "Redeliver", + confirmText: "Replay this delivery? The receiver will get the original payload again — they must be idempotent on the X-Objectstack-Delivery header.", + successMessage: "Queued for redelivery" + } + } + } +}; diff --git a/packages/plugins/plugin-webhooks/src/translations/es-ES.objects.generated.ts b/packages/plugins/plugin-webhooks/src/translations/es-ES.objects.generated.ts new file mode 100644 index 000000000..9edb94a50 --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/es-ES.objects.generated.ts @@ -0,0 +1,187 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const esESObjects: NonNullable = { + sys_webhook: { + label: "Webhook", + pluralLabel: "Webhooks", + description: "Suscripción saliente de Webhook HTTP. Se crea mediante defineWebhook() en código o con el editor de Studio; la ejecuta el plugin del conector HTTP.", + fields: { + id: { + label: "ID de webhook" + }, + name: { + label: "Nombre", + help: "Nombre snake_case único; se usa en los registros y en la auditoría." + }, + label: { + label: "Nombre visible" + }, + object_name: { + label: "Objeto", + help: "Nombre corto del objeto cuyos eventos activan este Webhook (vacío = activación manual / API)." + }, + triggers: { + label: "Desencadenantes", + help: "Lista de eventos separada por comas: create,update,delete,undelete,api." + }, + url: { + label: "URL de destino", + help: "Endpoint externo que recibe el POST." + }, + method: { + label: "Método HTTP", + help: "GET / POST / PUT / PATCH / DELETE" + }, + description: { + label: "Descripción" + }, + active: { + label: "Activo", + help: "Los Webhooks inactivos se omiten en el despachador." + }, + definition_json: { + label: "Definición", + help: "JSON serializado de Webhook (consulte @objectstack/spec/automation/webhook): configuración completa de cabeceras/auth/reintentos/payload." + }, + created_at: { + label: "Creado el" + }, + updated_at: { + label: "Actualizado el" + } + }, + _views: { + active: { + label: "Activo" + }, + inactive: { + label: "Inactivo" + }, + by_object: { + label: "Por objeto" + }, + all_webhooks: { + label: "Todos" + } + } + }, + sys_webhook_delivery: { + label: "Webhook Delivery", + pluralLabel: "Webhook Deliveries", + description: "Durable outbox row for one webhook attempt. Managed by @objectstack/plugin-webhooks; do not write directly.", + fields: { + id: { + label: "Delivery ID", + help: "UUID — also doubles as the receiver-side idempotency key" + }, + webhook_id: { + label: "Webhook ID", + help: "FK to sys_webhook.id (loosely coupled — denormalised URL/secret on row)" + }, + event_id: { + label: "Event ID", + help: "Source event id; UNIQUE(event_id, webhook_id) for dedup" + }, + event_type: { + label: "Event Type", + help: "e.g. data.record.created" + }, + url: { + label: "Target URL", + help: "Snapshotted at enqueue so config edits do not rewrite live rows" + }, + method: { + label: "Method" + }, + headers_json: { + label: "Headers JSON" + }, + secret: { + label: "HMAC Secret" + }, + timeout_ms: { + label: "Timeout (ms)" + }, + payload_json: { + label: "Payload JSON" + }, + partition_key: { + label: "Partition", + help: "hash(webhook_id) mod partitionCount — precomputed for cheap WHERE" + }, + status: { + label: "Status", + help: "pending | in_flight | success | failed | dead" + }, + attempts: { + label: "Attempts", + help: "Number of POST attempts made so far" + }, + claimed_by: { + label: "Claimed By" + }, + claimed_at: { + label: "Claimed At (ms)" + }, + next_retry_at: { + label: "Next Retry At (ms)" + }, + last_attempted_at: { + label: "Last Attempted At (ms)" + }, + response_code: { + label: "HTTP Status" + }, + response_body: { + label: "Response Body (capped)" + }, + error: { + label: "Error" + }, + created_at: { + label: "Created At (ms)" + }, + updated_at: { + label: "Updated At (ms)" + } + }, + _views: { + recent: { + label: "Recent" + }, + failures: { + label: "Failures" + }, + in_flight: { + label: "In Flight" + }, + pending: { + label: "Pending" + }, + by_status: { + label: "By Status" + }, + by_webhook: { + label: "By Webhook" + }, + all_deliveries: { + label: "All" + } + }, + _actions: { + redeliver: { + label: "Redeliver", + confirmText: "Replay this delivery? The receiver will get the original payload again — they must be idempotent on the X-Objectstack-Delivery header.", + successMessage: "Queued for redelivery" + } + } + } +}; diff --git a/packages/plugins/plugin-webhooks/src/translations/index.ts b/packages/plugins/plugin-webhooks/src/translations/index.ts new file mode 100644 index 000000000..fb1115bd0 --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * WebhooksTranslations — i18n bundle owned by this plugin (ADR-0029 D8). + * + * Object label/field/view/action translations for the sys_* objects this + * plugin owns. Loaded at runtime via the plugin's `kernel:ready` hook + * (`i18n.loadTranslations`). Regenerate with `os i18n extract` against + * `scripts/i18n-extract.config.ts`. + */ + +import type { TranslationBundle } from '@objectstack/spec/system'; +import { enObjects } from './en.objects.generated.js'; +import { zhCNObjects } from './zh-CN.objects.generated.js'; +import { jaJPObjects } from './ja-JP.objects.generated.js'; +import { esESObjects } from './es-ES.objects.generated.js'; + +export const WebhooksTranslations: TranslationBundle = { + en: { objects: enObjects }, + 'zh-CN': { objects: zhCNObjects }, + 'ja-JP': { objects: jaJPObjects }, + 'es-ES': { objects: esESObjects }, +}; diff --git a/packages/plugins/plugin-webhooks/src/translations/ja-JP.objects.generated.ts b/packages/plugins/plugin-webhooks/src/translations/ja-JP.objects.generated.ts new file mode 100644 index 000000000..9e0efc89c --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/ja-JP.objects.generated.ts @@ -0,0 +1,187 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const jaJPObjects: NonNullable = { + sys_webhook: { + label: "Webhook", + pluralLabel: "Webhook", + description: "送信 HTTP Webhook サブスクリプション。defineWebhook() またはスタジオエディタで作成し、HTTP コネクタプラグインが実行します。", + fields: { + id: { + label: "Webhook ID" + }, + name: { + label: "名前", + help: "一意の snake_case 名 — ログおよび監査で参照" + }, + label: { + label: "表示名" + }, + object_name: { + label: "オブジェクト", + help: "このウェブフックを発火するイベントのオブジェクト短縮名(空白 = 手動 / API トリガー)" + }, + triggers: { + label: "トリガー", + help: "カンマ区切りのイベントリスト: create,update,delete,undelete,api" + }, + url: { + label: "ターゲット URL", + help: "POST を受信する外部エンドポイント" + }, + method: { + label: "HTTP メソッド", + help: "GET / POST / PUT / PATCH / DELETE" + }, + description: { + label: "説明" + }, + active: { + label: "有効", + help: "無効にするとディスパッチャにスキップされます" + }, + definition_json: { + label: "定義", + help: "シリアライズされた Webhook JSON(@objectstack/spec/automation/webhook 参照)— ヘッダー/認証/リトライ/ペイロード設定を含む" + }, + created_at: { + label: "作成日時" + }, + updated_at: { + label: "更新日時" + } + }, + _views: { + active: { + label: "有効" + }, + inactive: { + label: "無効" + }, + by_object: { + label: "オブジェクト別" + }, + all_webhooks: { + label: "すべて" + } + } + }, + sys_webhook_delivery: { + label: "Webhook Delivery", + pluralLabel: "Webhook Deliveries", + description: "Durable outbox row for one webhook attempt. Managed by @objectstack/plugin-webhooks; do not write directly.", + fields: { + id: { + label: "Delivery ID", + help: "UUID — also doubles as the receiver-side idempotency key" + }, + webhook_id: { + label: "Webhook ID", + help: "FK to sys_webhook.id (loosely coupled — denormalised URL/secret on row)" + }, + event_id: { + label: "Event ID", + help: "Source event id; UNIQUE(event_id, webhook_id) for dedup" + }, + event_type: { + label: "Event Type", + help: "e.g. data.record.created" + }, + url: { + label: "Target URL", + help: "Snapshotted at enqueue so config edits do not rewrite live rows" + }, + method: { + label: "Method" + }, + headers_json: { + label: "Headers JSON" + }, + secret: { + label: "HMAC Secret" + }, + timeout_ms: { + label: "Timeout (ms)" + }, + payload_json: { + label: "Payload JSON" + }, + partition_key: { + label: "Partition", + help: "hash(webhook_id) mod partitionCount — precomputed for cheap WHERE" + }, + status: { + label: "Status", + help: "pending | in_flight | success | failed | dead" + }, + attempts: { + label: "Attempts", + help: "Number of POST attempts made so far" + }, + claimed_by: { + label: "Claimed By" + }, + claimed_at: { + label: "Claimed At (ms)" + }, + next_retry_at: { + label: "Next Retry At (ms)" + }, + last_attempted_at: { + label: "Last Attempted At (ms)" + }, + response_code: { + label: "HTTP Status" + }, + response_body: { + label: "Response Body (capped)" + }, + error: { + label: "Error" + }, + created_at: { + label: "Created At (ms)" + }, + updated_at: { + label: "Updated At (ms)" + } + }, + _views: { + recent: { + label: "Recent" + }, + failures: { + label: "Failures" + }, + in_flight: { + label: "In Flight" + }, + pending: { + label: "Pending" + }, + by_status: { + label: "By Status" + }, + by_webhook: { + label: "By Webhook" + }, + all_deliveries: { + label: "All" + } + }, + _actions: { + redeliver: { + label: "Redeliver", + confirmText: "Replay this delivery? The receiver will get the original payload again — they must be idempotent on the X-Objectstack-Delivery header.", + successMessage: "Queued for redelivery" + } + } + } +}; diff --git a/packages/plugins/plugin-webhooks/src/translations/zh-CN.objects.generated.ts b/packages/plugins/plugin-webhooks/src/translations/zh-CN.objects.generated.ts new file mode 100644 index 000000000..6be1736c1 --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/zh-CN.objects.generated.ts @@ -0,0 +1,187 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const zhCNObjects: NonNullable = { + sys_webhook: { + label: "Webhook", + pluralLabel: "Webhook", + description: "外发 HTTP Webhook 订阅。可在代码中通过 defineWebhook() 编写,或在 Studio 编辑器中维护;由 HTTP 连接器插件执行。", + fields: { + id: { + label: "Webhook ID" + }, + name: { + label: "名称", + help: "唯一的 snake_case 名称——用于日志和审计引用" + }, + label: { + label: "显示标签" + }, + object_name: { + label: "对象", + help: "触发该 Webhook 的短对象名(留空 = 手动 / API 触发)" + }, + triggers: { + label: "触发器", + help: "以逗号分隔的事件列表:create,update,delete,undelete,api" + }, + url: { + label: "目标 URL", + help: "接收 POST 请求的外部端点" + }, + method: { + label: "HTTP 方法", + help: "GET / POST / PUT / PATCH / DELETE" + }, + description: { + label: "描述" + }, + active: { + label: "启用", + help: "停用的 Webhook 会被调度器跳过" + }, + definition_json: { + label: "定义", + help: "序列化的 Webhook JSON(参见 @objectstack/spec/automation/webhook)——包含完整的 headers/auth/retry/payload 配置" + }, + created_at: { + label: "创建时间" + }, + updated_at: { + label: "更新时间" + } + }, + _views: { + active: { + label: "启用" + }, + inactive: { + label: "停用" + }, + by_object: { + label: "按对象" + }, + all_webhooks: { + label: "全部" + } + } + }, + sys_webhook_delivery: { + label: "Webhook Delivery", + pluralLabel: "Webhook Deliveries", + description: "Durable outbox row for one webhook attempt. Managed by @objectstack/plugin-webhooks; do not write directly.", + fields: { + id: { + label: "Delivery ID", + help: "UUID — also doubles as the receiver-side idempotency key" + }, + webhook_id: { + label: "Webhook ID", + help: "FK to sys_webhook.id (loosely coupled — denormalised URL/secret on row)" + }, + event_id: { + label: "Event ID", + help: "Source event id; UNIQUE(event_id, webhook_id) for dedup" + }, + event_type: { + label: "Event Type", + help: "e.g. data.record.created" + }, + url: { + label: "Target URL", + help: "Snapshotted at enqueue so config edits do not rewrite live rows" + }, + method: { + label: "Method" + }, + headers_json: { + label: "Headers JSON" + }, + secret: { + label: "HMAC Secret" + }, + timeout_ms: { + label: "Timeout (ms)" + }, + payload_json: { + label: "Payload JSON" + }, + partition_key: { + label: "Partition", + help: "hash(webhook_id) mod partitionCount — precomputed for cheap WHERE" + }, + status: { + label: "Status", + help: "pending | in_flight | success | failed | dead" + }, + attempts: { + label: "Attempts", + help: "Number of POST attempts made so far" + }, + claimed_by: { + label: "Claimed By" + }, + claimed_at: { + label: "Claimed At (ms)" + }, + next_retry_at: { + label: "Next Retry At (ms)" + }, + last_attempted_at: { + label: "Last Attempted At (ms)" + }, + response_code: { + label: "HTTP Status" + }, + response_body: { + label: "Response Body (capped)" + }, + error: { + label: "Error" + }, + created_at: { + label: "Created At (ms)" + }, + updated_at: { + label: "Updated At (ms)" + } + }, + _views: { + recent: { + label: "Recent" + }, + failures: { + label: "Failures" + }, + in_flight: { + label: "In Flight" + }, + pending: { + label: "Pending" + }, + by_status: { + label: "By Status" + }, + by_webhook: { + label: "By Webhook" + }, + all_deliveries: { + label: "All" + } + }, + _actions: { + redeliver: { + label: "Redeliver", + confirmText: "Replay this delivery? The receiver will get the original payload again — they must be idempotent on the X-Objectstack-Delivery header.", + successMessage: "Queued for redelivery" + } + } + } +}; diff --git a/packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts b/packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts index 164be2162..4a710a59e 100644 --- a/packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts +++ b/packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts @@ -158,6 +158,22 @@ export class WebhookOutboxPlugin implements Plugin { ); } + // ADR-0029 D8 — contribute this plugin's object translations to the + // i18n service on kernel:ready (the i18n plugin may register later). + if (typeof (ctx as any).hook === 'function') { + (ctx as any).hook('kernel:ready', async () => { + try { + const i18n = ctx.getService('i18n'); + if (i18n && typeof i18n.loadTranslations === 'function') { + const { WebhooksTranslations } = await import('./translations/index.js'); + for (const [locale, data] of Object.entries(WebhooksTranslations)) { + i18n.loadTranslations(locale, data as Record); + } + } + } catch { /* i18n optional */ } + }); + } + const outbox = this.resolveOutbox(ctx); this.outboxInstance = outbox; const nodeId =