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
33 changes: 33 additions & 0 deletions .changeset/metadata-translation-audit.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
---
"@objectstack/spec": patch
"@objectstack/rest": patch
"@objectstack/platform-objects": patch
"@objectstack/service-settings": patch
---

Fix system-metadata translations: locale fallback, app/dashboard localization, and coverage gaps.

Switching the UI language left many surfaces in English. Three root causes
are addressed:

- **Locale fallback (server).** The metadata translation resolver
(`@objectstack/spec` `i18n-resolver`) now resolves a requested locale
against the locales actually present in the bundle (exact →
case-insensitive → base-language → variant), so a request for `zh`
correctly hits the `zh-CN` bundle instead of falling back to English.
This mirrors `resolveLocale` in `@objectstack/core` and benefits every
resolver (objects, views, actions, settings, metadata forms).

- **App & dashboard localization (server).** Added `translateApp` and
`translateDashboard` resolvers and wired `app`/`dashboard` into the REST
`/meta` translation path. App labels, sidebar/navigation group labels,
and dashboard titles/widgets were previously never localized at the API
boundary even though the translation data existed.

- **Coverage & quality (data).** Added translations for the previously
untranslated platform objects `sys_share_link`, `sys_view_definition`,
and `sys_metadata_audit` (and registered them in the i18n-extract config
so future extractions keep them). Replaced English placeholder strings
left in the `zh-CN` / `ja-JP` / `es-ES` object and metadata-form bundles
(notably action `confirmText` / `successMessage` prompts). Added the
missing `es-ES` built-in Settings bundle in `@objectstack/service-settings`.
11 changes: 10 additions & 1 deletion packages/platform-objects/scripts/i18n-extract.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,6 +58,7 @@ import {
SysRolePermissionSet,
SysRecordShare,
SysSharingRule,
SysShareLink,
} from '../src/security/index.js';

// ── Audit ─────────────────────────────────────────────────────────────────
Expand All@@ -84,7 +85,12 @@ import {
import { SysWebhook } from '../src/integration/index.js';

// ── Metadata ──────────────────────────────────────────────────────────────
import { SysMetadataObject, SysMetadataHistoryObject } from '../src/metadata/index.js';
import {
SysMetadataObject,
SysMetadataHistoryObject,
SysViewDefinitionObject,
SysMetadataAuditObject,
} from '../src/metadata/index.js';

// ── System ────────────────────────────────────────────────────────────────
import { SysSetting, SysSecret, SysSettingAudit } from '../src/system/index.js';
Expand DownExpand Up@@ -147,6 +153,7 @@ export default defineStack({
SysRolePermissionSet,
SysRecordShare,
SysSharingRule,
SysShareLink,

// Audit
SysAuditLog,
Expand All@@ -172,6 +179,8 @@ export default defineStack({
// Metadata
SysMetadataObject,
SysMetadataHistoryObject,
SysViewDefinitionObject,
SysMetadataAuditObject,

// System
SysSetting,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -1556,6 +1556,100 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
}
}
},
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",
Expand DownExpand Up@@ -2861,6 +2955,147 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
}
}
},
sys_view_definition: {
label: "View Definition",
pluralLabel: "View Definitions",
description: "Runtime-authored view definitions (shared / personal layers). The package layer ships from source.",
fields: {
id: {
label: "ID"
},
name: {
label: "Name"
},
object: {
label: "Object"
},
view_kind: {
label: "View Kind",
options: {
list: "list",
form: "form"
}
},
label: {
label: "Label"
},
is_default: {
label: "Is Default"
},
view_order: {
label: "Order"
},
scope: {
label: "Scope",
options: {
shared: "shared",
personal: "personal"
}
},
owner: {
label: "Owner"
},
hidden: {
label: "Hidden"
},
config: {
label: "Config",
help: "ListView or FormView configuration (matches spec ViewItem.config)."
},
organization_id: {
label: "Organization",
help: "Organization for multi-tenant isolation."
},
state: {
label: "State",
options: {
draft: "draft",
active: "active",
archived: "archived"
}
},
created_by: {
label: "Created By"
},
created_at: {
label: "Created At"
},
updated_by: {
label: "Updated By"
},
updated_at: {
label: "Updated At"
}
}
},
sys_metadata_audit: {
label: "Metadata Audit",
pluralLabel: "Metadata Audit",
description: "Append-only audit trail of metadata write decisions (ADR-0010).",
fields: {
id: {
label: "ID"
},
occurred_at: {
label: "Occurred At"
},
actor: {
label: "Actor",
help: "Acting principal — user id, system id, or \"system\"."
},
source: {
label: "Source"
},
type: {
label: "Metadata Type"
},
name: {
label: "Name"
},
organization_id: {
label: "Organization"
},
operation: {
label: "Operation",
options: {
save: "save",
publish: "publish",
rollback: "rollback",
delete: "delete",
reset: "reset"
}
},
outcome: {
label: "Outcome",
options: {
allowed: "allowed",
denied: "denied",
forced: "forced"
}
},
code: {
label: "Code"
},
lock_state: {
label: "Lock State",
options: {
none: "none",
"no-overlay": "no-overlay",
"no-delete": "no-delete",
full: "full"
}
},
lock_overridden: {
label: "Lock Overridden"
},
request_id: {
label: "Request ID"
},
note: {
label: "Note"
}
}
},
sys_setting: {
label: "Setting",
pluralLabel: "Settings",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,7 +857,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "Traducción"
},
router: {
label: "Router"
label: "Enrutador"
},
function: {
label: "Función"
Expand Down
Loading
Loading