Skip to content

Commit 253d898

Browse files
committed
i18n
1 parent c580bf2 commit 253d898

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

packages/cli/src/commands/serve.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,29 @@ export default class Serve extends Command {
217217
(p: any) => p.name === 'com.objectstack.service.i18n'
218218
|| p.constructor?.name === 'I18nServicePlugin'
219219
);
220+
// Check the top-level config AND any nested AppPlugin bundles in the
221+
// `plugins` array — host/aggregator configs (e.g. apps/server) don't
222+
// define translations themselves but compose multiple `new AppPlugin(...)`
223+
// entries, each carrying its own translations.
224+
const pluginBundleHasTranslations = (bundle: any): boolean => {
225+
if (!bundle || typeof bundle !== 'object') return false;
226+
if (Array.isArray(bundle.translations) && bundle.translations.length > 0) return true;
227+
if (bundle.i18n) return true;
228+
if (bundle.manifest && (
229+
(Array.isArray(bundle.manifest.translations) && bundle.manifest.translations.length > 0)
230+
|| bundle.manifest.i18n
231+
)) return true;
232+
return false;
233+
};
234+
const anyAppPluginHasTranslations = plugins.some((p: any) => {
235+
if (!p) return false;
236+
// AppPlugin instances expose their bundle on `.bundle`
237+
if (p.bundle && pluginBundleHasTranslations(p.bundle)) return true;
238+
return false;
239+
});
220240
const configHasTranslations = (
221-
(Array.isArray(config.translations) && config.translations.length > 0)
222-
|| config.i18n
223-
|| (config.manifest && (
224-
(Array.isArray(config.manifest.translations) && config.manifest.translations.length > 0)
225-
|| config.manifest.i18n
226-
))
241+
pluginBundleHasTranslations(config)
242+
|| anyAppPluginHasTranslations
227243
);
228244
if (!hasI18nPlugin && configHasTranslations) {
229245
try {

packages/core/src/fallbacks/memory-i18n.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

3+
/**
4+
* Recursively merge `source` into `target`. Nested plain objects are merged
5+
* rather than replaced, so multiple plugins can each contribute their own
6+
* slice of a locale's translations (e.g. `{objects: {account: ...}}` and
7+
* `{objects: {task: ...}}`) without clobbering one another.
8+
*/
9+
function deepMerge(
10+
target: Record<string, unknown>,
11+
source: Record<string, unknown>,
12+
): Record<string, unknown> {
13+
const result: Record<string, unknown> = { ...target };
14+
for (const key of Object.keys(source)) {
15+
const tVal = target[key];
16+
const sVal = source[key];
17+
if (
18+
tVal && sVal
19+
&& typeof tVal === 'object' && !Array.isArray(tVal)
20+
&& typeof sVal === 'object' && !Array.isArray(sVal)
21+
) {
22+
result[key] = deepMerge(
23+
tVal as Record<string, unknown>,
24+
sVal as Record<string, unknown>,
25+
);
26+
} else {
27+
result[key] = sVal;
28+
}
29+
}
30+
return result;
31+
}
32+
333
/**
434
* Resolve a locale code against available locales with fallback.
535
*
@@ -93,8 +123,12 @@ export function createMemoryI18n() {
93123
},
94124

95125
loadTranslations(locale: string, data: Record<string, unknown>): void {
96-
const existing = translations.get(locale) ?? {};
97-
translations.set(locale, { ...existing, ...data });
126+
const existing = translations.get(locale);
127+
if (existing) {
128+
translations.set(locale, deepMerge(existing, data));
129+
} else {
130+
translations.set(locale, { ...data });
131+
}
98132
},
99133

100134
getLocales(): string[] {

0 commit comments

Comments
 (0)