Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/i18n-sso-scim-userposition-importjob-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@objectstack/platform-objects": patch
"@objectstack/plugin-security": patch
---

fix(i18n): translate the SSO / SCIM / user-position / import-job admin objects

Four live, UI-facing system objects were registered but never added to their
package's i18n extract config, so non-English admins saw raw English `label`
metadata:

- `sys_sso_provider`, `sys_scim_provider` (platform-objects) — identity-provider
admin grids plus the register / verify-domain actions.
- `sys_user_position` (plugin-security) — delegated position assignment
(`userActions` create/edit/delete); its sibling `sys_user_permission_set` was
already translated, so this closes an inconsistency.
- `sys_import_job` (platform-objects) — import history / progress, alongside the
already-translated `sys_job` / `sys_job_run`.

Adds each object to its package's `scripts/i18n-extract.config.ts` and supplies
real zh-CN / ja-JP / es-ES translations across all four locale bundles, and
extends the bundle-ownership guards' `OWNED_OBJECTS` to cover them. The
orphan-only guards from #3502 could not catch this "owned-and-live-but-never-
extracted" gap.
6 changes: 6 additions & 0 deletions packages/platform-objects/scripts/i18n-extract.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import {
SysOauthClientResource,
SysOauthClientAssertion,
SysJwks,
SysSsoProvider,
SysScimProvider,
} from '../src/identity/index.js';

// ── Security ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -80,6 +82,7 @@ import {
SysJob,
SysJobRun,
SysJobQueue,
SysImportJob,
} from '../src/audit/index.js';

// ── Integration ───────────────────────────────────────────────────────────
Expand Down Expand Up @@ -151,6 +154,8 @@ export default defineStack({
SysOauthClientResource,
SysOauthClientAssertion,
SysJwks,
SysSsoProvider,
SysScimProvider,

// Security: RBAC moved to @objectstack/plugin-security, sharing to
// @objectstack/plugin-sharing (ADR-0029 K2 / D8).
Expand All @@ -168,6 +173,7 @@ export default defineStack({
SysJob,
SysJobRun,
SysJobQueue,
SysImportJob,

// Integration: sys_webhook moved to @objectstack/plugin-webhooks (ADR-0029 D8).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ const OWNED_OBJECTS = new Set([
'sys_user_preference', 'sys_oauth_application', 'sys_oauth_access_token',
'sys_oauth_refresh_token', 'sys_oauth_consent', 'sys_oauth_resource',
'sys_oauth_client_resource', 'sys_oauth_client_assertion', 'sys_jwks',
// identity — external SSO / SCIM providers (admin-facing, better-auth-managed)
'sys_sso_provider', 'sys_scim_provider',
// audit / messaging-adjacent (still owned here)
'sys_notification', 'sys_attachment', 'sys_email', 'sys_email_template',
'sys_saved_report', 'sys_report_schedule', 'sys_job', 'sys_job_run', 'sys_job_queue',
'sys_import_job',
// metadata
'sys_metadata', 'sys_metadata_history', 'sys_view_definition', 'sys_metadata_audit',
// system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,203 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
}
}
},
sys_sso_provider: {
label: "SSO Provider",
pluralLabel: "SSO Providers",
description: "External SSO identity providers (OIDC / SAML) this environment federates login to",
fields: {
id: {
label: "ID"
},
provider_id: {
label: "Provider ID",
help: "Stable provider identifier (unique within the environment)"
},
issuer: {
label: "Issuer",
help: "IdP issuer URL"
},
domain: {
label: "Email Domain",
help: "Email domain routed to this IdP (e.g. acme.com)"
},
domain_verified: {
label: "Domain Verified",
help: "Whether DNS ownership of the email domain has been proven (ADR-0024 ②). Set by “Verify Domain” after the DNS TXT record resolves. Managed by better-auth — not directly editable. Only enforced when domain verification is enabled for the environment."
},
oidc_config: {
label: "OIDC Config",
help: "JSON: clientId, clientSecret, endpoints, scopes, mapping, pkce (managed by better-auth)"
},
saml_config: {
label: "SAML Config",
help: "JSON: entryPoint, cert, identifierFormat, mapping (managed by better-auth)"
},
user_id: {
label: "Registered By",
help: "User who registered this provider"
},
organization_id: {
label: "Organization",
help: "Organization scope (when org-scoped SSO is used)"
},
created_at: {
label: "Created At"
},
updated_at: {
label: "Updated At"
}
},
_views: {
all: {
label: "All",
emptyState: {
title: "No SSO providers yet",
message: "Register your organization’s external IdP — OIDC (Okta, Entra, Auth0, …) with “Register SSO Provider”, or SAML 2.0 with “Register SAML Provider”. Members whose email domain matches can then sign in through it."
}
}
},
_actions: {
register_sso_provider: {
label: "Register SSO Provider",
params: {
providerId: {
label: "Provider ID",
helpText: "Stable identifier, e.g. \"okta\" or \"acme-entra\"."
},
issuer: {
label: "Issuer URL",
helpText: "IdP issuer, e.g. https://acme.okta.com. Discovery is fetched from here unless an explicit URL is given below."
},
domain: {
label: "Email Domain",
helpText: "Users with this email domain are routed to this IdP, e.g. acme.com."
},
clientId: {
label: "Client ID",
helpText: "OAuth client ID issued by the IdP for this environment."
},
clientSecret: {
label: "Client Secret",
helpText: "OAuth client secret (stored encrypted by better-auth)."
},
discoveryEndpoint: {
label: "Discovery URL",
helpText: "Optional. OIDC discovery document URL. Leave blank to derive `<issuer>/.well-known/openid-configuration`."
},
scopes: {
label: "Scopes",
helpText: "Optional. Space- or comma-separated OAuth scopes. Defaults to \"openid email profile\".",
placeholder: "openid email profile"
},
mapId: {
label: "Map: User ID claim",
helpText: "Optional. ID-token claim mapped to the user ID. Defaults to \"sub\".",
placeholder: "sub"
},
mapEmail: {
label: "Map: Email claim",
helpText: "Optional. Claim mapped to email. Defaults to \"email\".",
placeholder: "email"
},
mapName: {
label: "Map: Name claim",
helpText: "Optional. Claim mapped to display name. Defaults to \"name\".",
placeholder: "name"
}
}
},
register_saml_provider: {
label: "Register SAML Provider",
params: {
providerId: {
label: "Provider ID",
helpText: "Stable identifier, e.g. \"acme-saml\"."
},
issuer: {
label: "IdP Entity ID",
helpText: "The IdP’s SAML EntityID (issuer), e.g. https://saml.acme.com/entityid."
},
domain: {
label: "Email Domain",
helpText: "Users with this email domain are routed to this IdP, e.g. acme.com."
},
entryPoint: {
label: "IdP SSO URL",
helpText: "The IdP’s SAML single sign-on (redirect) endpoint that receives the SAMLRequest."
},
cert: {
label: "IdP Signing Certificate",
helpText: "The IdP’s X.509 signing certificate (PEM body). Used to verify assertion signatures."
},
identifierFormat: {
label: "NameID Format",
helpText: "Optional. Requested SAML NameID format. Defaults to the IdP’s configured format.",
placeholder: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
}
}
},
request_domain_verification: {
label: "Request Domain Verification",
resultDialog: {
title: "Verify your domain",
description: "Add the DNS TXT record below at your domain’s DNS provider, then run “Verify Domain”. The token is shown once.",
acknowledge: "Done",
fields: {
dnsRecordType: "Record type",
dnsRecordName: "Name / Host",
dnsRecordValue: "Value"
}
}
},
verify_domain: {
label: "Verify Domain",
successMessage: "Domain ownership verified"
},
delete_sso_provider: {
label: "Delete SSO Provider",
confirmText: "Delete this SSO provider? Users from its domain will no longer be able to sign in through it.",
successMessage: "SSO provider deleted"
}
}
},
sys_scim_provider: {
label: "SCIM Provider",
pluralLabel: "SCIM Providers",
description: "SCIM 2.0 connections (bearer tokens) external IdPs use to provision/deprovision this environment's users",
fields: {
id: {
label: "ID"
},
provider_id: {
label: "Provider ID",
help: "Stable SCIM provider identifier (e.g. \"okta-scim\")"
},
scim_token: {
label: "SCIM Token (hash)",
help: "Hashed bearer credential for this SCIM connection — the plaintext is shown once at generate-token. Sensitive; do not expose."
},
organization_id: {
label: "Organization",
help: "Organization scope of this token (org-scoped tokens restrict provisioning to that org)"
},
user_id: {
label: "Owned By",
help: "User who generated this token (when provider-ownership is enabled)"
},
created_at: {
label: "Created At"
},
updated_at: {
label: "Updated At"
}
},
_views: {
all: {
label: "All"
}
}
},
sys_notification: {
label: "Notification",
pluralLabel: "Notifications",
Expand Down Expand Up @@ -2236,6 +2433,92 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
}
}
},
sys_import_job: {
label: "Import Job",
pluralLabel: "Import Jobs",
description: "Asynchronous bulk-import job state, progress, and history",
fields: {
id: {
label: "Job ID"
},
object_name: {
label: "Object",
help: "API name of the object being imported into"
},
status: {
label: "Status",
options: {
pending: "pending",
running: "running",
succeeded: "succeeded",
failed: "failed",
cancelled: "cancelled"
}
},
total_rows: {
label: "Total Rows"
},
processed_rows: {
label: "Processed Rows"
},
created_count: {
label: "Created"
},
updated_count: {
label: "Updated"
},
skipped_count: {
label: "Skipped"
},
error_count: {
label: "Errors"
},
write_mode: {
label: "Write Mode",
options: {
insert: "insert",
update: "update",
upsert: "upsert"
}
},
dry_run: {
label: "Dry Run"
},
run_automations: {
label: "Run Automations"
},
treat_as_historical: {
label: "Treat As Historical"
},
error: {
label: "Fatal Error"
},
results: {
label: "Row Results (sample)",
help: "Capped sample of per-row results (failures first) for the UI"
},
undo_log: {
label: "Undo Log",
help: "Reversal instructions ({created:[ids], updated:[{id,before}]}) captured for small non-dry-run jobs so the import can be undone"
},
reverted_at: {
label: "Reverted At",
help: "Set when the import was undone (created records deleted, updated records restored)"
},
started_at: {
label: "Started At"
},
completed_at: {
label: "Completed At"
},
created_by: {
label: "Created By"
},
created_at: {
label: "Created At"
}
}
},
sys_metadata: {
label: "System Metadata",
pluralLabel: "System Metadata",
Expand Down
Loading