From 2b5d51a485aed536d51a6a2030e6a439cb162850 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 18:36:35 +0000
Subject: [PATCH 1/3] Initial plan
From ebbfca86d8230c3d365f4c65e2c53f8720697124 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 19:12:44 +0000
Subject: [PATCH 2/3] Fix I18nLabel rendering: resolve labels in ConsoleLayout,
AppSidebar, CommandPalette, DashboardRenderer, AppManagementPage, and
SpecCompliance test
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
.../src/__tests__/SpecCompliance.test.tsx | 4 ++--
apps/console/src/components/AppSidebar.tsx | 4 ++--
.../console/src/components/CommandPalette.tsx | 21 +++++++++---------
apps/console/src/components/ConsoleLayout.tsx | 5 +++--
.../src/pages/system/AppManagementPage.tsx | 5 +++--
.../src/DashboardRenderer.tsx | 22 +++++++++++++------
6 files changed, 36 insertions(+), 25 deletions(-)
diff --git a/apps/console/src/__tests__/SpecCompliance.test.tsx b/apps/console/src/__tests__/SpecCompliance.test.tsx
index c335849f9..af7666e0a 100644
--- a/apps/console/src/__tests__/SpecCompliance.test.tsx
+++ b/apps/console/src/__tests__/SpecCompliance.test.tsx
@@ -25,7 +25,7 @@ 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);
// Name convention: lowercase snake_case
expect(app.name).toMatch(/^[a-z][a-z0-9_]*$/);
@@ -36,7 +36,7 @@ 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 (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 8a533e71d..f25a5183a 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 f2f41f356..a28d5ec1e 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 75a9c4d49..4f739cc73 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 4efa787a3..466bf4b1f 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 94f9b5f3c..23c2cafd2 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 && (
From 50adabab7511e0c9dd7f8a4d84f2a03b3be4da4e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 19:29:09 +0000
Subject: [PATCH 3/3] Improve SpecCompliance test to validate I18nLabel shape
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
apps/console/src/__tests__/SpecCompliance.test.tsx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/apps/console/src/__tests__/SpecCompliance.test.tsx b/apps/console/src/__tests__/SpecCompliance.test.tsx
index af7666e0a..feed99dc1 100644
--- a/apps/console/src/__tests__/SpecCompliance.test.tsx
+++ b/apps/console/src/__tests__/SpecCompliance.test.tsx
@@ -26,6 +26,9 @@ describe('ObjectStack Spec v0.9.0 Compliance', () => {
expect(typeof app.name).toBe('string');
expect(app.label).toBeDefined();
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_]*$/);
@@ -37,6 +40,9 @@ describe('ObjectStack Spec v0.9.0 Compliance', () => {
// Optional fields that should be defined if present
if (app.description) {
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');