Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions api/[[...route]].ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,46 @@ import { ProductsTranslations } from '../packages/products/dist/translations/ind
import { SupportTranslations } from '../packages/support/dist/translations/index.js';
import { HRTranslations } from '../packages/hr/dist/translations/index.js';

/**
* Merge multiple TranslationBundles into a single bundle by deep-merging each locale.
* This allows multiple plugins to contribute translations that are combined into one namespace.
*/
function mergeTranslationBundles(...bundles: any[]): any {
const merged: any = {};

for (const bundle of bundles) {
for (const locale in bundle) {
if (!merged[locale]) {
merged[locale] = {};
}

// Deep merge the translation data for this locale
const source = bundle[locale];
const target = merged[locale];

for (const key in source) {
if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
target[key] = { ...target[key], ...source[key] };
} else {
target[key] = source[key];
}
}
}
}

return merged;
}

// Merge all plugin translations into a single bundle
const HotCRMTranslations = mergeTranslationBundles(
CrmTranslations,
FinanceTranslations,
MarketingTranslations,
ProductsTranslations,
SupportTranslations,
HRTranslations
);

// ---------------------------------------------------------------------------
// Timeout constants — protect against permanently-pending promises that would
// cause Vercel's 60 s function timeout.
Expand Down Expand Up @@ -481,17 +521,11 @@ async function bootstrap(): Promise<Hono> {
},
i18n: {
defaultLocale: 'en',
supportedLocales: ['en', 'zh-CN', 'ja-JP'],
supportedLocales: ['en', 'zh', 'ja'],
fallbackLocale: 'en',
namespace: 'hotcrm',
translations: HotCRMTranslations,
},
translations: [
CrmTranslations,
FinanceTranslations,
MarketingTranslations,
ProductsTranslations,
SupportTranslations,
HRTranslations,
],
objects: [],
plugins: [],
})), PLUGIN_TIMEOUT_MS, 'AppPlugin-manifest');
Expand Down
57 changes: 43 additions & 14 deletions objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,46 @@ import { ProductsTranslations } from './packages/products/dist/translations/inde
import { SupportTranslations } from './packages/support/dist/translations/index.js';
import { HRTranslations } from './packages/hr/dist/translations/index.js';

/**
* Merge multiple TranslationBundles into a single bundle by deep-merging each locale.
* This allows multiple plugins to contribute translations that are combined into one namespace.
*/
function mergeTranslationBundles(...bundles: any[]): any {
const merged: any = {};

for (const bundle of bundles) {
for (const locale in bundle) {
if (!merged[locale]) {
merged[locale] = {};
}

// Deep merge the translation data for this locale
const source = bundle[locale];
const target = merged[locale];

for (const key in source) {
if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
target[key] = { ...target[key], ...source[key] };
} else {
target[key] = source[key];
}
}
}
}

return merged;
}

// Merge all plugin translations into a single bundle
const HotCRMTranslations = mergeTranslationBundles(
CrmTranslations,
FinanceTranslations,
MarketingTranslations,
ProductsTranslations,
SupportTranslations,
HRTranslations
);

/**
* HotCRM Application Configuration
*
Expand All @@ -55,9 +95,10 @@ export default defineStack({
// Internationalization (i18n) configuration
i18n: {
defaultLocale: 'en',
supportedLocales: ['en', 'zh-CN', 'ja-JP'],
supportedLocales: ['en', 'zh', 'ja'],
fallbackLocale: 'en',
fileOrganization: 'per_locale',
namespace: 'hotcrm',
translations: HotCRMTranslations,
},

// Empty objects array triggers auto-loading of ObjectQL and the memory driver,
Expand All @@ -75,18 +116,6 @@ export default defineStack({
LanguageDataset,
],

// Aggregated translations from all business plugins (TranslationBundle[])
// Each plugin also registers its own translations for plugin-level loading.
// The root config merges them for the AppPlugin to load at startup.
translations: [
CrmTranslations,
FinanceTranslations,
MarketingTranslations,
ProductsTranslations,
SupportTranslations,
HRTranslations,
],

// Register all Business Plugins
// Core clouds (6)
plugins: [
Expand Down
10 changes: 5 additions & 5 deletions packages/crm/__tests__/unit/schemas/i18n-translation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ describe('CRM i18n Translations', () => {

it('should contain all supported locales', () => {
expect(CrmTranslations).toHaveProperty('en');
expect(CrmTranslations).toHaveProperty('zh-CN');
expect(CrmTranslations).toHaveProperty('ja-JP');
expect(CrmTranslations).toHaveProperty('zh');
expect(CrmTranslations).toHaveProperty('ja');
});

it('should have non-empty objects section for each locale', () => {
for (const locale of ['en', 'zh-CN', 'ja-JP']) {
for (const locale of ['en', 'zh', 'ja']) {
const data = CrmTranslations[locale];
expect(data).toBeDefined();
expect(data.objects).toBeDefined();
Expand All @@ -36,8 +36,8 @@ describe('CRM i18n Translations', () => {

it('should have consistent object keys across all locales', () => {
const enKeys = Object.keys(CrmTranslations.en.objects ?? {}).sort();
const zhKeys = Object.keys(CrmTranslations['zh-CN'].objects ?? {}).sort();
const jaKeys = Object.keys(CrmTranslations['ja-JP'].objects ?? {}).sort();
const zhKeys = Object.keys(CrmTranslations['zh'].objects ?? {}).sort();
const jaKeys = Object.keys(CrmTranslations['ja'].objects ?? {}).sort();
expect(enKeys).toEqual(zhKeys);
expect(enKeys).toEqual(jaKeys);
});
Expand Down
12 changes: 6 additions & 6 deletions packages/crm/src/translations/crm.translation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { TranslationBundle } from '@objectstack/spec/system';
import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { zh } from './zh.js';
import { ja } from './ja.js';

/**
* CRM Sales Cloud — Internationalization (i18n)
*
* Demonstrates **per-locale file splitting** convention:
* each language is defined in its own file (`en.ts`, `zh-CN.ts`, `ja-JP.ts`)
* each language is defined in its own file (`en.ts`, `zh.ts`, `ja.ts`)
* and assembled into a single `TranslationBundle` here.
*
* Enterprise-grade multi-language translations covering:
Expand All @@ -16,10 +16,10 @@ import { jaJP } from './ja-JP.js';
* - App & navigation group labels
* - Common UI messages, validation messages
*
* Supported locales: en, zh-CN, ja-JP
* Supported locales: en, zh, ja
*/
export const CrmTranslations: TranslationBundle = {
en,
'zh-CN': zhCN,
'ja-JP': jaJP,
zh,
ja,
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TranslationData } from '@objectstack/spec/system';

/**
* 日本語 (ja-JP) — CRM Sales Cloud Translations
* 日本語 (ja) — CRM Sales Cloud Translations
*
* Per-locale file: one file per language, following the `per_locale` convention.
*/
export const jaJP: TranslationData = {
export const ja: TranslationData = {
objects: {
account: {
label: '取引先',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TranslationData } from '@objectstack/spec/system';

/**
* 简体中文 (zh-CN) — CRM Sales Cloud Translations
* 简体中文 (zh) — CRM Sales Cloud Translations
*
* Per-locale file: one file per language, following the `per_locale` convention.
*/
export const zhCN: TranslationData = {
export const zh: TranslationData = {
objects: {
account: {
label: '客户',
Expand Down
10 changes: 5 additions & 5 deletions packages/finance/__tests__/unit/schemas/i18n-translation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ describe('Finance i18n Translations', () => {

it('should contain all supported locales', () => {
expect(FinanceTranslations).toHaveProperty('en');
expect(FinanceTranslations).toHaveProperty('zh-CN');
expect(FinanceTranslations).toHaveProperty('ja-JP');
expect(FinanceTranslations).toHaveProperty('zh');
expect(FinanceTranslations).toHaveProperty('ja');
});

it('should have non-empty objects section for each locale', () => {
for (const locale of ['en', 'zh-CN', 'ja-JP']) {
for (const locale of ['en', 'zh', 'ja']) {
const data = FinanceTranslations[locale];
expect(data).toBeDefined();
expect(data.objects).toBeDefined();
Expand All @@ -35,8 +35,8 @@ describe('Finance i18n Translations', () => {

it('should have consistent object keys across all locales', () => {
const enKeys = Object.keys(FinanceTranslations.en.objects ?? {}).sort();
const zhKeys = Object.keys(FinanceTranslations['zh-CN'].objects ?? {}).sort();
const jaKeys = Object.keys(FinanceTranslations['ja-JP'].objects ?? {}).sort();
const zhKeys = Object.keys(FinanceTranslations['zh'].objects ?? {}).sort();
const jaKeys = Object.keys(FinanceTranslations['ja'].objects ?? {}).sort();
expect(enKeys).toEqual(zhKeys);
expect(enKeys).toEqual(jaKeys);
});
Expand Down
10 changes: 5 additions & 5 deletions packages/finance/src/translations/finance.translation.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { TranslationBundle } from '@objectstack/spec/system';
import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { zh } from './zh.js';
import { ja } from './ja.js';

/**
* Finance — Internationalization (i18n)
*
* Per-locale file splitting convention.
* Supported locales: en, zh-CN, ja-JP
* Supported locales: en, zh, ja
*/
export const FinanceTranslations: TranslationBundle = {
en,
'zh-CN': zhCN,
'ja-JP': jaJP,
zh,
ja,
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TranslationData } from '@objectstack/spec/system';

/**
* 日本語 (ja-JP) — Finance Cloud Translations
* 日本語 (ja) — Finance Cloud Translations
*
* Per-locale file: one file per language, following the `per_locale` convention.
*/
export const jaJP: TranslationData = {
export const ja: TranslationData = {
objects: {
contract: {
label: '契約',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TranslationData } from '@objectstack/spec/system';

/**
* 简体中文 (zh-CN) — Finance Cloud Translations
* 简体中文 (zh) — Finance Cloud Translations
*
* Per-locale file: one file per language, following the `per_locale` convention.
*/
export const zhCN: TranslationData = {
export const zh: TranslationData = {
objects: {
contract: {
label: '合同',
Expand Down
10 changes: 5 additions & 5 deletions packages/hr/__tests__/unit/schemas/i18n-translation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ describe('HR i18n Translations', () => {

it('should contain all supported locales', () => {
expect(HRTranslations).toHaveProperty('en');
expect(HRTranslations).toHaveProperty('zh-CN');
expect(HRTranslations).toHaveProperty('ja-JP');
expect(HRTranslations).toHaveProperty('zh');
expect(HRTranslations).toHaveProperty('ja');
});

it('should have non-empty objects section for each locale', () => {
for (const locale of ['en', 'zh-CN', 'ja-JP']) {
for (const locale of ['en', 'zh', 'ja']) {
const data = HRTranslations[locale];
expect(data).toBeDefined();
expect(data.objects).toBeDefined();
Expand All @@ -35,8 +35,8 @@ describe('HR i18n Translations', () => {

it('should have consistent object keys across all locales', () => {
const enKeys = Object.keys(HRTranslations.en.objects ?? {}).sort();
const zhKeys = Object.keys(HRTranslations['zh-CN'].objects ?? {}).sort();
const jaKeys = Object.keys(HRTranslations['ja-JP'].objects ?? {}).sort();
const zhKeys = Object.keys(HRTranslations['zh'].objects ?? {}).sort();
const jaKeys = Object.keys(HRTranslations['ja'].objects ?? {}).sort();
expect(enKeys).toEqual(zhKeys);
expect(enKeys).toEqual(jaKeys);
});
Expand Down
10 changes: 5 additions & 5 deletions packages/hr/src/translations/hr.translation.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { TranslationBundle } from '@objectstack/spec/system';
import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { zh } from './zh.js';
import { ja } from './ja.js';

/**
* Human Capital Management — Internationalization (i18n)
*
* Per-locale file splitting convention.
* Supported locales: en, zh-CN, ja-JP
* Supported locales: en, zh, ja
*/
export const HRTranslations: TranslationBundle = {
en,
'zh-CN': zhCN,
'ja-JP': jaJP,
zh,
ja,
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TranslationData } from '@objectstack/spec/system';

/**
* 日本語 (ja-JP) — Human Capital Management Translations
* 日本語 (ja) — Human Capital Management Translations
*
* Per-locale file: one file per language, following the `per_locale` convention.
*/
export const jaJP: TranslationData = {
export const ja: TranslationData = {
objects: {
department: {
label: '部門',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TranslationData } from '@objectstack/spec/system';

/**
* 简体中文 (zh-CN) — Human Capital Management Translations
* 简体中文 (zh) — Human Capital Management Translations
*
* Per-locale file: one file per language, following the `per_locale` convention.
*/
export const zhCN: TranslationData = {
export const zh: TranslationData = {
objects: {
department: {
label: '部门',
Expand Down
Loading
Loading