diff --git a/apps/console/src/__tests__/SpecCompliance.test.tsx b/apps/console/src/__tests__/SpecCompliance.test.tsx index c335849f..feed99dc 100644 --- a/apps/console/src/__tests__/SpecCompliance.test.tsx +++ b/apps/console/src/__tests__/SpecCompliance.test.tsx @@ -25,7 +25,10 @@ describe('ObjectStack Spec v0.9.0 Compliance', () => { expect(app.name).toBeDefined(); expect(typeof app.name).toBe('string'); expect(app.label).toBeDefined(); - expect(typeof app.label).toBe('string'); + expect(['string', 'object']).toContain(typeof app.label); + if (typeof app.label === 'object') { + expect(app.label).toHaveProperty('key'); + } // Name convention: lowercase snake_case expect(app.name).toMatch(/^[a-z][a-z0-9_]*$/); @@ -36,7 +39,10 @@ describe('ObjectStack Spec v0.9.0 Compliance', () => { // Optional fields that should be defined if present if (app.description) { - expect(typeof app.description).toBe('string'); + expect(['string', 'object']).toContain(typeof app.description); + if (typeof app.description === 'object') { + expect(app.description).toHaveProperty('key'); + } } if (app.version) { expect(typeof app.version).toBe('string'); diff --git a/apps/console/src/components/AppSidebar.tsx b/apps/console/src/components/AppSidebar.tsx index 8a533e71..f25a5183 100644 --- a/apps/console/src/components/AppSidebar.tsx +++ b/apps/console/src/components/AppSidebar.tsx @@ -301,7 +301,7 @@ export function AppSidebar({ activeAppName, onAppChange }: { activeAppName: stri
{app.icon ? React.createElement(getIcon(app.icon), { className: "size-3" }) : }
- {app.label} + {resolveI18nLabel(app.label, t)} {activeApp.name === app.name && } ))} @@ -530,7 +530,7 @@ export function AppSidebar({ activeAppName, onAppChange }: { activeAppName: stri return ( - {item.label} + {resolveI18nLabel(item.label, t)} ); })} diff --git a/apps/console/src/components/CommandPalette.tsx b/apps/console/src/components/CommandPalette.tsx index f2f41f35..a28d5ec1 100644 --- a/apps/console/src/components/CommandPalette.tsx +++ b/apps/console/src/components/CommandPalette.tsx @@ -33,6 +33,7 @@ import { import { useTheme } from './theme-provider'; import { useExpressionContext, evaluateVisibility } from '../context/ExpressionProvider'; import { useObjectTranslation } from '@object-ui/i18n'; +import { resolveI18nLabel } from '../utils'; /** Resolve a Lucide icon by name (kebab-case or PascalCase) */ function getIcon(name?: string): React.ElementType { @@ -101,11 +102,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange return ( runCommand(() => navigate(`${baseUrl}/${item.objectName}`))} > - {item.label} + {resolveI18nLabel(item.label, t)} ); })} @@ -120,11 +121,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange .map(item => ( runCommand(() => navigate(`${baseUrl}/dashboard/${item.dashboardName}`))} > - {item.label} + {resolveI18nLabel(item.label, t)} ))} @@ -138,11 +139,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange .map(item => ( runCommand(() => navigate(`${baseUrl}/page/${item.pageName}`))} > - {item.label} + {resolveI18nLabel(item.label, t)} ))} @@ -156,11 +157,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange .map(item => ( runCommand(() => navigate(`${baseUrl}/report/${item.reportName}`))} > - {item.label} + {resolveI18nLabel(item.label, t)} ))} @@ -178,11 +179,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange return ( runCommand(() => onAppChange(app.name))} > - {app.label} + {resolveI18nLabel(app.label, t)} {app.name === activeApp?.name && ( {t('console.commandPalette.current')} )} diff --git a/apps/console/src/components/ConsoleLayout.tsx b/apps/console/src/components/ConsoleLayout.tsx index 75a9c4d4..4f739cc7 100644 --- a/apps/console/src/components/ConsoleLayout.tsx +++ b/apps/console/src/components/ConsoleLayout.tsx @@ -11,6 +11,7 @@ import { AppShell } from '@object-ui/layout'; import { AppSidebar } from './AppSidebar'; import { AppHeader } from './AppHeader'; import { useResponsiveSidebar } from '../hooks/useResponsiveSidebar'; +import { resolveI18nLabel } from '../utils'; import type { ConnectionState } from '../dataSource'; interface ConsoleLayoutProps { @@ -46,7 +47,7 @@ export function ConsoleLayout({ } navbar={ @@ -60,7 +61,7 @@ export function ConsoleLayout({ favicon: activeApp.branding.favicon, logo: activeApp.branding.logo, title: activeApp.label - ? `${activeApp.label} — ObjectStack Console` + ? `${resolveI18nLabel(activeApp.label)} — ObjectStack Console` : undefined, } : undefined diff --git a/apps/console/src/pages/system/AppManagementPage.tsx b/apps/console/src/pages/system/AppManagementPage.tsx index 4efa787a..466bf4b1 100644 --- a/apps/console/src/pages/system/AppManagementPage.tsx +++ b/apps/console/src/pages/system/AppManagementPage.tsx @@ -27,6 +27,7 @@ import { } from 'lucide-react'; import { toast } from 'sonner'; import { useMetadata } from '../../context/MetadataProvider'; +import { resolveI18nLabel } from '../../utils'; export function AppManagementPage() { const navigate = useNavigate(); @@ -208,14 +209,14 @@ export function AppManagementPage() {
- {app.label || app.name} + {resolveI18nLabel(app.label) || app.name} {isDefault && Default} {isActive ? 'Active' : 'Inactive'}
{app.description && ( -

{app.description}

+

{resolveI18nLabel(app.description)}

)}
diff --git a/packages/plugin-dashboard/src/DashboardRenderer.tsx b/packages/plugin-dashboard/src/DashboardRenderer.tsx index 94f9b5f3..23c2cafd 100644 --- a/packages/plugin-dashboard/src/DashboardRenderer.tsx +++ b/packages/plugin-dashboard/src/DashboardRenderer.tsx @@ -13,6 +13,13 @@ import { forwardRef, useState, useEffect, useCallback, useRef } from 'react'; import { RefreshCw } from 'lucide-react'; import { isObjectProvider } from './utils'; +/** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */ +function resolveLabel(label: string | { key?: string; defaultValue?: string } | undefined): string | undefined { + if (label === undefined || label === null) return undefined; + if (typeof label === 'string') return label; + return label.defaultValue || label.key; +} + // Color palette for charts const CHART_COLORS = [ 'hsl(var(--chart-1))', @@ -248,7 +255,8 @@ export const DashboardRenderer = forwardRef handleWidgetClick(e, widget.id), onKeyDown: (e: React.KeyboardEvent) => handleWidgetKeyDown(e, widget.id, index), } : {}; @@ -305,10 +313,10 @@ export const DashboardRenderer = forwardRef - {widget.title && ( + {resolvedTitle && ( - - {widget.title} + + {resolvedTitle} )} @@ -325,10 +333,10 @@ export const DashboardRenderer = forwardRef {schema.header.showTitle !== false && schema.title && ( -

{schema.title}

+

{resolveLabel(schema.title)}

)} {schema.header.showDescription !== false && schema.description && ( -

{schema.description}

+

{resolveLabel(schema.description)}

)} {schema.header.actions && schema.header.actions.length > 0 && (