Skip to content

fix: spec-aligned CRM i18n using I18nLabel objects and API-based translation loading#878

Merged
hotlong merged 6 commits intomainfrom
copilot/fix-crm-internationalization-issue
Feb 26, 2026
Merged

fix: spec-aligned CRM i18n using I18nLabel objects and API-based translation loading#878
hotlong merged 6 commits intomainfrom
copilot/fix-crm-internationalization-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 26, 2026

CRM metadata used plain string labels instead of spec-aligned I18nLabel objects. The console had no mechanism to translate them, and language switching had no effect on CRM labels.

Per feedback, the console is a metadata-driven platform and must not import CRM code directly. Instead, CRM metadata now uses spec-aligned I18nLabel objects ({ key, defaultValue }) and translations are loaded via API.

Changes

  • CRM metadata (crm.app.ts, crm.dashboard.ts) — All navigation labels, app name/description, and dashboard widget titles now use I18nLabel objects (e.g. { key: 'crm.navigation.dashboard', defaultValue: 'Dashboard' }) instead of plain strings
  • NavigationItem / NavigationArea types (app.ts) — label field updated to support string | { key, defaultValue, params? } (I18nLabel union), aligning with @objectstack/spec
  • NavigationRenderer.tsx — Added exported resolveLabel() helper that resolves I18nLabel objects to plain strings (defaultValue fallback)
  • AppSchemaRenderer.tsx — Uses resolveLabel() for area and mobile navigation labels
  • utils.ts (console) — resolveI18nLabel(label, t?) now accepts an optional t() function for i18n translation lookup
  • AppSidebar.tsx / DashboardView.tsx — Pass t() from useObjectTranslation to resolveI18nLabel for runtime translation
  • main.tsx — Added loadLanguage callback that fetches translations from /api/v1/i18n/:lang via the I18nProvider's lazy-loading API
  • Mock server (browser.ts, server.ts) — Added /api/v1/i18n/:lang endpoint that serves application locale bundles from installed example packages
  • Tests — Updated CRM metadata tests and console test mocks for I18nLabel support; 183 tests pass across 7 test suites
Original prompt

console界面上crm国际化未生效,排查原因


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Canceled Canceled Feb 26, 2026 6:06pm
objectui-demo Error Error Feb 26, 2026 6:06pm
objectui-storybook Ready Ready Preview, Comment Feb 26, 2026 6:06pm

Request Review

…/dashboard labels

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong
Copy link
Copy Markdown
Collaborator

hotlong commented Feb 26, 2026

@copilot crm只是一个元数据的例子,console是元数据驱动的平台,console只会通过api加载crn中的元数据包括翻译,中不应该有crm的代码。请确认spec中关于国际化的规范。

Copilot AI changed the title [WIP] Fix CRM internationalization not working in console interface fix: load CRM i18n locales into console I18nProvider Feb 26, 2026
… approach

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ec-aligned i18n

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… logging

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 26, 2026

@copilot crm只是一个元数据的例子,console是元数据驱动的平台,console只会通过api加载crn中的元数据包括翻译,中不应该有crm的代码。请确认spec中关于国��...

已按照spec规范重新实现。CRM元数据现在使用I18nLabel对象({ key, defaultValue })代替纯字符串,console通过/api/v1/i18n/:lang API端点加载翻译资源,不再直接导入CRM代码。

关键变更(commit c92afa8):

  • CRM元数据(crm.app.tscrm.dashboard.ts):所有label改为I18nLabel对象
  • NavigationItem/NavigationArea类型:label字段支持I18nLabel联合类型
  • Console resolveI18nLabel(label, t):通过i18n t()函数解析翻译
  • I18nProvider:使用loadLanguage回调从API加载翻译
  • Mock server:添加/api/v1/i18n/:lang端点提供翻译数据

Console中零CRM特定代码。

Copilot AI changed the title fix: load CRM i18n locales into console I18nProvider fix: spec-aligned CRM i18n using I18nLabel objects and API-based translation loading Feb 26, 2026
@hotlong hotlong marked this pull request as ready for review February 26, 2026 17:51
Copilot AI review requested due to automatic review settings February 26, 2026 17:51
@hotlong hotlong merged commit cdb95e4 into main Feb 26, 2026
1 of 4 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements spec-aligned internationalization for CRM metadata by migrating from plain string labels to I18nLabel objects ({ key, defaultValue, params? }) and establishing an API-based translation loading mechanism.

Changes:

  • CRM metadata (app, dashboard, navigation) now uses I18nLabel objects for all user-facing text
  • Navigation types updated to support I18nLabel union type alongside plain strings
  • Translation utilities enhanced to accept optional t() function for i18n resolution
  • API endpoint /api/v1/i18n/:lang added to serve application translations dynamically
  • Console components (AppSidebar, DashboardView) updated to resolve I18nLabel objects for app/dashboard-level labels

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/types/src/app.ts Updated NavigationItem and NavigationArea label types to support I18nLabel objects
packages/layout/src/NavigationRenderer.tsx Added resolveLabel() helper and applied it to all label rendering points
packages/layout/src/AppSchemaRenderer.tsx Uses resolveLabel() for area switcher and mobile navigation
examples/crm/src/apps/crm.app.ts Converted all navigation labels to I18nLabel objects with crm.* translation keys
examples/crm/src/dashboards/crm.dashboard.ts Converted dashboard label and all widget titles to I18nLabel objects
examples/crm/src/tests/crm-metadata.test.ts Added resolveTitle() helper for test assertions on I18nLabel fields
apps/console/src/utils.ts Enhanced resolveI18nLabel() to accept optional t() function for i18n resolution
apps/console/src/main.tsx Added loadLanguage callback that fetches translations from API endpoint
apps/console/src/mocks/browser.ts Added /api/v1/i18n/:lang handler that loads CRM locales
apps/console/src/mocks/server.ts Added /api/v1/i18n/:lang handler for Node test environment
apps/console/src/components/DashboardView.tsx Added useObjectTranslation() hook and resolves dashboard-level labels
apps/console/src/components/AppSidebar.tsx Passes t() to resolveI18nLabel for app label/description resolution
apps/console/src/tests/AppSidebar.test.tsx Updated mock for resolveI18nLabel and added useObjectTranslation mock
apps/console/src/tests/app-creation-integration.test.tsx Updated resolveI18nLabel mock to handle I18nLabel objects
ROADMAP.md Documented P1 i18n implementation completion

name: 'crm_dashboard',
label: 'CRM Overview',
label: { key: 'crm.dashboard.title', defaultValue: 'CRM Overview' },
description: 'Revenue metrics, pipeline analytics, and deal insights',
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dashboard description field is a plain string, while the label field uses an I18nLabel object. For consistency and complete i18n coverage, the description should also use an I18nLabel object like { key: 'crm.dashboard.description', defaultValue: 'Revenue metrics, pipeline analytics, and deal insights' }. This ensures all user-facing text in the dashboard metadata can be translated.

Suggested change
description: 'Revenue metrics, pipeline analytics, and deal insights',
description: { key: 'crm.dashboard.description', defaultValue: 'Revenue metrics, pipeline analytics, and deal insights' },

Copilot uses AI. Check for mistakes.
Comment on lines 269 to +280
style={primaryColor ? { backgroundColor: primaryColor } : undefined}
>
{logo ? (
<img src={logo} alt={resolveI18nLabel(activeApp.label)} className="size-6 object-contain" />
<img src={logo} alt={resolveI18nLabel(activeApp.label, t)} className="size-6 object-contain" />
) : (
React.createElement(getIcon(activeApp.icon), { className: "size-4" })
)}
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{resolveI18nLabel(activeApp.label)}</span>
<span className="truncate font-semibold">{resolveI18nLabel(activeApp.label, t)}</span>
<span className="truncate text-xs">
{resolveI18nLabel(activeApp.description) || `${activeApps.length} Apps Available`}
{resolveI18nLabel(activeApp.description, t) || `${activeApps.length} Apps Available`}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mobile bottom navigation renders item.label directly (line 533) without resolving I18nLabel objects. Navigation items from CRM use I18nLabel objects (e.g., { key: 'crm.navigation.dashboard', defaultValue: 'Dashboard' }), which will display as [object Object] in the mobile UI. This should use resolveI18nLabel(item.label, t) like the rest of AppSidebar does (see lines 272, 278, 280).

Copilot uses AI. Check for mistakes.
Comment on lines +134 to 137
const { t } = useObjectTranslation();
const [isLoading, setIsLoading] = useState(true);
const [configPanelOpen, setConfigPanelOpen] = useState(false);
const [selectedWidgetId, setSelectedWidgetId] = useState<string | null>(null);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Widget titles in the CRM dashboard use I18nLabel objects, but the flattenWidgetConfig function treats widget.title as a plain string (line 82). When a widget title is an I18nLabel object like { key: 'crm.dashboard.widgets.totalRevenue', defaultValue: 'Total Revenue' }, it should be resolved using resolveI18nLabel(widget.title, t) before being flattened. This ensures the config panel displays the translated title and the saved config preserves the I18nLabel structure when needed.

Copilot uses AI. Check for mistakes.
Comment on lines 383 to 391
{/* ── Header ───────────────────────────────────────────────── */}
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-3 sm:gap-4 p-4 sm:p-6 border-b shrink-0">
<div className="min-w-0 flex-1">
<h1 className="text-lg sm:text-xl md:text-2xl font-bold tracking-tight truncate">{resolveI18nLabel(dashboard.label) || dashboard.name}</h1>
<h1 className="text-lg sm:text-xl md:text-2xl font-bold tracking-tight truncate">{resolveI18nLabel(dashboard.label, t) || dashboard.name}</h1>
{dashboard.description && (
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">{resolveI18nLabel(dashboard.description)}</p>
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">{resolveI18nLabel(dashboard.description, t)}</p>
)}
</div>
<div className="shrink-0 flex items-center gap-1.5">
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dashboard label and description are correctly resolved with resolveI18nLabel(dashboard.label, t), but widget titles also use I18nLabel objects in the CRM dashboard metadata and need similar resolution. The current implementation passes widget schemas directly to DashboardRenderer, which will render I18nLabel objects as [object Object] strings. Consider preprocessing the schema to resolve all I18nLabel fields in widgets before passing to DashboardRenderer, similar to how NavigationRenderer handles labels.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants