Skip to content

Commit b836c5c

Browse files
Copilothotlong
andcommitted
fix(i18n): add i18n bundle aggregation to root objectstack.config.ts
Root cause: `pnpm dev` from the monorepo root runs `objectstack serve --dev` which loads the ROOT objectstack.config.ts — not apps/console/objectstack.config.ts. The root config collects plugin configs via getConfig() and composes them with composeStacks(), but composeStacks() doesn't handle the custom `i18n` field. So translation data from CRM (and other stacks) was lost during composition, and AppPlugin.loadTranslations() found nothing to load. Fix: 1. Aggregate i18n bundles from all plugin configs (same pattern as objectstack.shared.ts) 2. Build spec-format translations array passed to AppPlugin(mergedApp) 3. Add MemoryI18nPlugin to register i18n service during init phase Verified by actually running the server and testing: - GET /api/v1/i18n/translations/zh → {"crm":{"objects":{"account":{"label":"客户"}},...}} - GET /api/v1/i18n/translations/en → {"crm":{"objects":{"account":{"label":"Account"}},...}} - GET /api/v1/i18n/locales → all 10 CRM locales Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/8b23b829-56d5-4f03-b8f4-fe066b662587
1 parent 2e842bb commit b836c5c

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919

2020
- **i18n Translations Empty in Server Mode (`pnpm start`)** (`apps/console`): Fixed translations returning `{}` when running the real ObjectStack server on port 3000. The root cause was that the CLI's `isHostConfig()` function detects plugins with `init` methods in the config's `plugins` array and treats it as a "host config" — **skipping auto-registration of `AppPlugin`**. Without `AppPlugin`, the config's translations, objects, and seed data were never loaded. Additionally, the kernel's memory i18n fallback is only auto-registered in `validateSystemRequirements()` (after all plugin starts), too late for `AppPlugin.start()``loadTranslations()`. Fixed by: (1) explicitly adding `AppPlugin(sharedConfig)` to `objectstack.config.ts` plugins, and (2) adding `MemoryI18nPlugin` before it to register the i18n service during init phase. Also added a spec-format `translations` array to `objectstack.shared.ts` so `AppPlugin.loadTranslations()` can iterate and load the CRM locale bundles.
2121

22+
- **i18n Translations Empty in Root Dev Mode (`pnpm dev`)** (root `objectstack.config.ts`): Fixed translations returning `{}` when running `pnpm dev` from the monorepo root. The root config uses `objectstack dev``objectstack serve --dev` which loads the root `objectstack.config.ts` — but this config did not aggregate i18n bundles from the example stacks (CRM, Todo, etc.). The `composeStacks()` function doesn't handle the custom `i18n` field, so translation data was lost during composition. Fixed by: (1) aggregating i18n bundles from all plugin configs (same pattern as `objectstack.shared.ts`), (2) building a spec-format `translations` array passed to `AppPlugin(mergedApp)`, and (3) adding `MemoryI18nPlugin` to register the i18n service during init phase.
23+
2224
- **Duplicate Data in Calendar and Kanban Views** (`@object-ui/plugin-view`, `@object-ui/plugin-kanban`, `@object-ui/plugin-list`): Fixed a bug where Calendar and Kanban views displayed every record twice. The root cause was that `ObjectView` (plugin-view) unconditionally fetched data even when a `renderListView` callback was provided — causing parallel duplicate requests since `ListView` (plugin-list) independently fetches its own data. The duplicate fetch results triggered re-renders that destabilised child component rendering, leading to duplicate events in the calendar and duplicate cards on the kanban board. Additionally, `ObjectKanban` lacked proper external-data handling (`data`/`loading` props with `hasExternalData` guard), unlike `ObjectCalendar` which already had this pattern. Fixes: (1) `ObjectView` now skips its own fetch when `renderListView` is provided, (2) `ObjectKanban` now accepts explicit `data`/`loading` props and skips internal fetch when external data is supplied (matching `ObjectCalendar`'s pattern), (3) `ListView` now handles `{ value: [] }` OData response format consistently with `extractRecords` utility. Includes regression tests.
2325

2426
- **CI Build Fix: Replace dynamic `require()` with static imports** (`@object-ui/plugin-view`): Replaced module-level `require('@object-ui/react')` calls in `index.tsx` and `ObjectView.tsx` with static `import` statements. The dynamic `require()` pattern is not supported by Next.js Turbopack, causing the docs site build to fail during SSR prerendering of the `/docs/components/complex/view-switcher` page with `Error: dynamic usage of require is not supported`. Since `@object-ui/react` is already a declared dependency and other files in the package use static imports from it, replacing the `require()` with static imports is safe and eliminates the SSR compatibility issue.

objectstack.config.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { AuthPlugin } from '@objectstack/plugin-auth';
2222
import { ConsolePlugin } from '@object-ui/console';
2323
import { composeStacks } from '@objectstack/spec';
2424
import { mergeViewsIntoObjects } from '@object-ui/core';
25+
import { createMemoryI18n } from '@objectstack/core';
2526
import { CRMPlugin } from './examples/crm/plugin';
2627
import { TodoPlugin } from './examples/todo/plugin';
2728
import { KitchenSinkPlugin } from './examples/kitchen-sink/plugin';
@@ -39,6 +40,24 @@ const allConfigs = plugins.map((p) => {
3940
// so we collect data from all stacks before composing).
4041
const allData = allConfigs.flatMap((c: any) => c.manifest?.data || c.data || []);
4142

43+
// Aggregate i18n bundles from all stacks that declare an i18n section.
44+
// Each bundle carries a namespace (e.g. 'crm') and per-language translations.
45+
const i18nBundles = allConfigs
46+
.map((c: any) => c.i18n)
47+
.filter((i: any) => i?.namespace && i?.translations);
48+
49+
// Build the spec `translations` array for AppPlugin.loadTranslations().
50+
// Format: Array<{ [locale]: { namespace: data } }>.
51+
// Each locale's data is nested under the bundle's namespace so that the
52+
// i18n service serves `{ crm: { objects: { account: { label: '客户' } } } }`.
53+
const specTranslations: Record<string, any>[] = i18nBundles.map((bundle: any) => {
54+
const result: Record<string, any> = {};
55+
for (const [locale, data] of Object.entries(bundle.translations)) {
56+
result[locale] = { [bundle.namespace]: data };
57+
}
58+
return result;
59+
});
60+
4261
// Protocol-level composition via @objectstack/spec: handles object dedup,
4362
// array concatenation, actions→objects mapping, and manifest selection.
4463
const composed = composeStacks(allConfigs as any[], { objectConflict: 'override' }) as any;
@@ -58,13 +77,37 @@ const mergedApp = {
5877
type: 'app',
5978
data: allData,
6079
},
80+
// Spec-format translations consumed by AppPlugin.loadTranslations() in server mode
81+
translations: specTranslations,
82+
i18n: {
83+
bundles: i18nBundles,
84+
defaultLocale: 'en',
85+
},
6186
};
6287

88+
/**
89+
* Registers the in-memory i18n service during the init phase (Phase 1)
90+
* so that AppPlugin.start() → loadTranslations() (Phase 2) can find
91+
* and load translation bundles. Without this, the kernel's memory i18n
92+
* fallback only registers in validateSystemRequirements() — too late.
93+
*/
94+
class MemoryI18nPlugin {
95+
readonly name = 'com.objectstack.service.i18n';
96+
readonly version = '1.0.0';
97+
readonly type = 'service' as const;
98+
99+
init(ctx: any) {
100+
const svc = createMemoryI18n();
101+
ctx.registerService('i18n', svc);
102+
}
103+
}
104+
63105
// Export only plugins — no top-level objects/manifest/apps.
64106
// The CLI auto-creates an AppPlugin from the config if it detects objects/manifest/apps,
65107
// which would conflict with our explicit AppPlugin and skip seed data loading.
66108
export default {
67109
plugins: [
110+
new MemoryI18nPlugin(),
68111
new ObjectQLPlugin(),
69112
new DriverPlugin(new InMemoryDriver()),
70113
new AppPlugin(mergedApp),

0 commit comments

Comments
 (0)