From be640f37b78c767c6d187c75a41f0044a6bfa0b2 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Tue, 24 Mar 2026 03:54:33 +0000 Subject: [PATCH 1/4] Initial plan From 237366cc22ea0f85a1721d496d097a803727caa9 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Tue, 24 Mar 2026 03:59:03 +0000 Subject: [PATCH 2/4] Fix i18n configuration by adding namespace and moving translations into i18n block Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> Agent-Logs-Url: https://github.com/objectstack-ai/hotcrm/sessions/45c9e772-4a27-44c2-a25e-1749458e6618 --- api/[[...route]].ts | 17 +++++++++-------- objectstack.config.ts | 22 +++++++++------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/api/[[...route]].ts b/api/[[...route]].ts index c812b9a7..60ab1aa7 100644 --- a/api/[[...route]].ts +++ b/api/[[...route]].ts @@ -483,15 +483,16 @@ async function bootstrap(): Promise { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN', 'ja-JP'], fallbackLocale: 'en', + namespace: 'hotcrm', + translations: [ + CrmTranslations, + FinanceTranslations, + MarketingTranslations, + ProductsTranslations, + SupportTranslations, + HRTranslations, + ], }, - translations: [ - CrmTranslations, - FinanceTranslations, - MarketingTranslations, - ProductsTranslations, - SupportTranslations, - HRTranslations, - ], objects: [], plugins: [], })), PLUGIN_TIMEOUT_MS, 'AppPlugin-manifest'); diff --git a/objectstack.config.ts b/objectstack.config.ts index 1114a8b7..3c44df6f 100644 --- a/objectstack.config.ts +++ b/objectstack.config.ts @@ -57,7 +57,15 @@ export default defineStack({ defaultLocale: 'en', supportedLocales: ['en', 'zh-CN', 'ja-JP'], fallbackLocale: 'en', - fileOrganization: 'per_locale', + namespace: 'hotcrm', + translations: [ + CrmTranslations, + FinanceTranslations, + MarketingTranslations, + ProductsTranslations, + SupportTranslations, + HRTranslations, + ], }, // Empty objects array triggers auto-loading of ObjectQL and the memory driver, @@ -75,18 +83,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: [ From ee2a79af6a11f634c99e49416fde00515a3c8711 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Tue, 24 Mar 2026 05:18:42 +0000 Subject: [PATCH 3/4] Change i18n to use short locale codes (zh, ja) instead of BCP 47 codes (zh-CN, ja-JP) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> Agent-Logs-Url: https://github.com/objectstack-ai/hotcrm/sessions/8dd51fb3-3edb-4696-8d1a-fa92fd248d20 --- api/[[...route]].ts | 2 +- objectstack.config.ts | 2 +- .../__tests__/unit/schemas/i18n-translation.test.ts | 10 +++++----- packages/crm/src/translations/crm.translation.ts | 12 ++++++------ packages/crm/src/translations/{ja-JP.ts => ja.ts} | 4 ++-- packages/crm/src/translations/{zh-CN.ts => zh.ts} | 4 ++-- .../__tests__/unit/schemas/i18n-translation.test.ts | 10 +++++----- .../finance/src/translations/finance.translation.ts | 10 +++++----- .../finance/src/translations/{ja-JP.ts => ja.ts} | 4 ++-- .../finance/src/translations/{zh-CN.ts => zh.ts} | 4 ++-- .../__tests__/unit/schemas/i18n-translation.test.ts | 10 +++++----- packages/hr/src/translations/hr.translation.ts | 10 +++++----- packages/hr/src/translations/{ja-JP.ts => ja.ts} | 4 ++-- packages/hr/src/translations/{zh-CN.ts => zh.ts} | 4 ++-- .../__tests__/unit/schemas/i18n-translation.test.ts | 10 +++++----- .../marketing/src/translations/{ja-JP.ts => ja.ts} | 4 ++-- .../src/translations/marketing.translation.ts | 10 +++++----- .../marketing/src/translations/{zh-CN.ts => zh.ts} | 4 ++-- .../__tests__/unit/schemas/i18n-translation.test.ts | 10 +++++----- .../products/src/translations/{ja-JP.ts => ja.ts} | 4 ++-- .../src/translations/products.translation.ts | 10 +++++----- .../products/src/translations/{zh-CN.ts => zh.ts} | 4 ++-- .../__tests__/unit/schemas/i18n-translation.test.ts | 10 +++++----- .../support/src/translations/{ja-JP.ts => ja.ts} | 4 ++-- .../support/src/translations/support.translation.ts | 10 +++++----- .../support/src/translations/{zh-CN.ts => zh.ts} | 4 ++-- 26 files changed, 87 insertions(+), 87 deletions(-) rename packages/crm/src/translations/{ja-JP.ts => ja.ts} (99%) rename packages/crm/src/translations/{zh-CN.ts => zh.ts} (99%) rename packages/finance/src/translations/{ja-JP.ts => ja.ts} (99%) rename packages/finance/src/translations/{zh-CN.ts => zh.ts} (99%) rename packages/hr/src/translations/{ja-JP.ts => ja.ts} (99%) rename packages/hr/src/translations/{zh-CN.ts => zh.ts} (99%) rename packages/marketing/src/translations/{ja-JP.ts => ja.ts} (99%) rename packages/marketing/src/translations/{zh-CN.ts => zh.ts} (99%) rename packages/products/src/translations/{ja-JP.ts => ja.ts} (99%) rename packages/products/src/translations/{zh-CN.ts => zh.ts} (99%) rename packages/support/src/translations/{ja-JP.ts => ja.ts} (99%) rename packages/support/src/translations/{zh-CN.ts => zh.ts} (99%) diff --git a/api/[[...route]].ts b/api/[[...route]].ts index 60ab1aa7..fbb1b40d 100644 --- a/api/[[...route]].ts +++ b/api/[[...route]].ts @@ -481,7 +481,7 @@ async function bootstrap(): Promise { }, i18n: { defaultLocale: 'en', - supportedLocales: ['en', 'zh-CN', 'ja-JP'], + supportedLocales: ['en', 'zh', 'ja'], fallbackLocale: 'en', namespace: 'hotcrm', translations: [ diff --git a/objectstack.config.ts b/objectstack.config.ts index 3c44df6f..6e3a9386 100644 --- a/objectstack.config.ts +++ b/objectstack.config.ts @@ -55,7 +55,7 @@ export default defineStack({ // Internationalization (i18n) configuration i18n: { defaultLocale: 'en', - supportedLocales: ['en', 'zh-CN', 'ja-JP'], + supportedLocales: ['en', 'zh', 'ja'], fallbackLocale: 'en', namespace: 'hotcrm', translations: [ diff --git a/packages/crm/__tests__/unit/schemas/i18n-translation.test.ts b/packages/crm/__tests__/unit/schemas/i18n-translation.test.ts index 2d296f77..da60bf5c 100644 --- a/packages/crm/__tests__/unit/schemas/i18n-translation.test.ts +++ b/packages/crm/__tests__/unit/schemas/i18n-translation.test.ts @@ -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(); @@ -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); }); diff --git a/packages/crm/src/translations/crm.translation.ts b/packages/crm/src/translations/crm.translation.ts index 9719800b..b3a5e5b9 100644 --- a/packages/crm/src/translations/crm.translation.ts +++ b/packages/crm/src/translations/crm.translation.ts @@ -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: @@ -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, }; diff --git a/packages/crm/src/translations/ja-JP.ts b/packages/crm/src/translations/ja.ts similarity index 99% rename from packages/crm/src/translations/ja-JP.ts rename to packages/crm/src/translations/ja.ts index a8a42515..54199b58 100644 --- a/packages/crm/src/translations/ja-JP.ts +++ b/packages/crm/src/translations/ja.ts @@ -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: '取引先', diff --git a/packages/crm/src/translations/zh-CN.ts b/packages/crm/src/translations/zh.ts similarity index 99% rename from packages/crm/src/translations/zh-CN.ts rename to packages/crm/src/translations/zh.ts index 1aca33ad..258d6bce 100644 --- a/packages/crm/src/translations/zh-CN.ts +++ b/packages/crm/src/translations/zh.ts @@ -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: '客户', diff --git a/packages/finance/__tests__/unit/schemas/i18n-translation.test.ts b/packages/finance/__tests__/unit/schemas/i18n-translation.test.ts index 85e72fb7..9986fc5d 100644 --- a/packages/finance/__tests__/unit/schemas/i18n-translation.test.ts +++ b/packages/finance/__tests__/unit/schemas/i18n-translation.test.ts @@ -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(); @@ -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); }); diff --git a/packages/finance/src/translations/finance.translation.ts b/packages/finance/src/translations/finance.translation.ts index 6ff77199..b69329e8 100644 --- a/packages/finance/src/translations/finance.translation.ts +++ b/packages/finance/src/translations/finance.translation.ts @@ -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, }; diff --git a/packages/finance/src/translations/ja-JP.ts b/packages/finance/src/translations/ja.ts similarity index 99% rename from packages/finance/src/translations/ja-JP.ts rename to packages/finance/src/translations/ja.ts index 2f271d60..1ef720a3 100644 --- a/packages/finance/src/translations/ja-JP.ts +++ b/packages/finance/src/translations/ja.ts @@ -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: '契約', diff --git a/packages/finance/src/translations/zh-CN.ts b/packages/finance/src/translations/zh.ts similarity index 99% rename from packages/finance/src/translations/zh-CN.ts rename to packages/finance/src/translations/zh.ts index 3430acb2..daaba5a1 100644 --- a/packages/finance/src/translations/zh-CN.ts +++ b/packages/finance/src/translations/zh.ts @@ -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: '合同', diff --git a/packages/hr/__tests__/unit/schemas/i18n-translation.test.ts b/packages/hr/__tests__/unit/schemas/i18n-translation.test.ts index 62bedb07..42f0039e 100644 --- a/packages/hr/__tests__/unit/schemas/i18n-translation.test.ts +++ b/packages/hr/__tests__/unit/schemas/i18n-translation.test.ts @@ -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(); @@ -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); }); diff --git a/packages/hr/src/translations/hr.translation.ts b/packages/hr/src/translations/hr.translation.ts index 2890bf9d..6bcc21ec 100644 --- a/packages/hr/src/translations/hr.translation.ts +++ b/packages/hr/src/translations/hr.translation.ts @@ -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, }; diff --git a/packages/hr/src/translations/ja-JP.ts b/packages/hr/src/translations/ja.ts similarity index 99% rename from packages/hr/src/translations/ja-JP.ts rename to packages/hr/src/translations/ja.ts index 3adcf5dd..8fe00f43 100644 --- a/packages/hr/src/translations/ja-JP.ts +++ b/packages/hr/src/translations/ja.ts @@ -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: '部門', diff --git a/packages/hr/src/translations/zh-CN.ts b/packages/hr/src/translations/zh.ts similarity index 99% rename from packages/hr/src/translations/zh-CN.ts rename to packages/hr/src/translations/zh.ts index cd507d16..41afabc9 100644 --- a/packages/hr/src/translations/zh-CN.ts +++ b/packages/hr/src/translations/zh.ts @@ -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: '部门', diff --git a/packages/marketing/__tests__/unit/schemas/i18n-translation.test.ts b/packages/marketing/__tests__/unit/schemas/i18n-translation.test.ts index a32d32c6..83ebbf24 100644 --- a/packages/marketing/__tests__/unit/schemas/i18n-translation.test.ts +++ b/packages/marketing/__tests__/unit/schemas/i18n-translation.test.ts @@ -13,12 +13,12 @@ describe('Marketing i18n Translations', () => { it('should contain all supported locales', () => { expect(MarketingTranslations).toHaveProperty('en'); - expect(MarketingTranslations).toHaveProperty('zh-CN'); - expect(MarketingTranslations).toHaveProperty('ja-JP'); + expect(MarketingTranslations).toHaveProperty('zh'); + expect(MarketingTranslations).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 = MarketingTranslations[locale]; expect(data).toBeDefined(); expect(data.objects).toBeDefined(); @@ -35,8 +35,8 @@ describe('Marketing i18n Translations', () => { it('should have consistent object keys across all locales', () => { const enKeys = Object.keys(MarketingTranslations.en.objects ?? {}).sort(); - const zhKeys = Object.keys(MarketingTranslations['zh-CN'].objects ?? {}).sort(); - const jaKeys = Object.keys(MarketingTranslations['ja-JP'].objects ?? {}).sort(); + const zhKeys = Object.keys(MarketingTranslations['zh'].objects ?? {}).sort(); + const jaKeys = Object.keys(MarketingTranslations['ja'].objects ?? {}).sort(); expect(enKeys).toEqual(zhKeys); expect(enKeys).toEqual(jaKeys); }); diff --git a/packages/marketing/src/translations/ja-JP.ts b/packages/marketing/src/translations/ja.ts similarity index 99% rename from packages/marketing/src/translations/ja-JP.ts rename to packages/marketing/src/translations/ja.ts index e5037179..a82e1dba 100644 --- a/packages/marketing/src/translations/ja-JP.ts +++ b/packages/marketing/src/translations/ja.ts @@ -1,11 +1,11 @@ import type { TranslationData } from '@objectstack/spec/system'; /** - * 日本語 (ja-JP) — Marketing Cloud Translations + * 日本語 (ja) — Marketing Cloud Translations * * Per-locale file: one file per language, following the `per_locale` convention. */ -export const jaJP: TranslationData = { +export const ja: TranslationData = { objects: { campaign: { label: 'キャンペーン', diff --git a/packages/marketing/src/translations/marketing.translation.ts b/packages/marketing/src/translations/marketing.translation.ts index e88fed87..feceae6b 100644 --- a/packages/marketing/src/translations/marketing.translation.ts +++ b/packages/marketing/src/translations/marketing.translation.ts @@ -1,7 +1,7 @@ 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'; /** * Marketing Cloud — Internationalization (i18n) @@ -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 MarketingTranslations: TranslationBundle = { en, - 'zh-CN': zhCN, - 'ja-JP': jaJP, + zh, + ja, }; diff --git a/packages/marketing/src/translations/zh-CN.ts b/packages/marketing/src/translations/zh.ts similarity index 99% rename from packages/marketing/src/translations/zh-CN.ts rename to packages/marketing/src/translations/zh.ts index 3cd37749..cfefddf3 100644 --- a/packages/marketing/src/translations/zh-CN.ts +++ b/packages/marketing/src/translations/zh.ts @@ -1,11 +1,11 @@ import type { TranslationData } from '@objectstack/spec/system'; /** - * 简体中文 (zh-CN) — Marketing Cloud Translations + * 简体中文 (zh) — Marketing Cloud Translations * * Per-locale file: one file per language, following the `per_locale` convention. */ -export const zhCN: TranslationData = { +export const zh: TranslationData = { objects: { campaign: { label: '营销活动', diff --git a/packages/products/__tests__/unit/schemas/i18n-translation.test.ts b/packages/products/__tests__/unit/schemas/i18n-translation.test.ts index a073d8b6..968b3ec2 100644 --- a/packages/products/__tests__/unit/schemas/i18n-translation.test.ts +++ b/packages/products/__tests__/unit/schemas/i18n-translation.test.ts @@ -13,12 +13,12 @@ describe('Products i18n Translations', () => { it('should contain all supported locales', () => { expect(ProductsTranslations).toHaveProperty('en'); - expect(ProductsTranslations).toHaveProperty('zh-CN'); - expect(ProductsTranslations).toHaveProperty('ja-JP'); + expect(ProductsTranslations).toHaveProperty('zh'); + expect(ProductsTranslations).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 = ProductsTranslations[locale]; expect(data).toBeDefined(); expect(data.objects).toBeDefined(); @@ -35,8 +35,8 @@ describe('Products i18n Translations', () => { it('should have consistent object keys across all locales', () => { const enKeys = Object.keys(ProductsTranslations.en.objects ?? {}).sort(); - const zhKeys = Object.keys(ProductsTranslations['zh-CN'].objects ?? {}).sort(); - const jaKeys = Object.keys(ProductsTranslations['ja-JP'].objects ?? {}).sort(); + const zhKeys = Object.keys(ProductsTranslations['zh'].objects ?? {}).sort(); + const jaKeys = Object.keys(ProductsTranslations['ja'].objects ?? {}).sort(); expect(enKeys).toEqual(zhKeys); expect(enKeys).toEqual(jaKeys); }); diff --git a/packages/products/src/translations/ja-JP.ts b/packages/products/src/translations/ja.ts similarity index 99% rename from packages/products/src/translations/ja-JP.ts rename to packages/products/src/translations/ja.ts index f8d901f3..78cf11a9 100644 --- a/packages/products/src/translations/ja-JP.ts +++ b/packages/products/src/translations/ja.ts @@ -1,11 +1,11 @@ import type { TranslationData } from '@objectstack/spec/system'; /** - * 日本語 (ja-JP) — Products & Pricing Translations + * 日本語 (ja) — Products & Pricing Translations * * Per-locale file: one file per language, following the `per_locale` convention. */ -export const jaJP: TranslationData = { +export const ja: TranslationData = { objects: { product: { label: '商品', diff --git a/packages/products/src/translations/products.translation.ts b/packages/products/src/translations/products.translation.ts index 59412799..08a2cccd 100644 --- a/packages/products/src/translations/products.translation.ts +++ b/packages/products/src/translations/products.translation.ts @@ -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'; /** * Products & Pricing — Internationalization (i18n) * * Per-locale file splitting convention. - * Supported locales: en, zh-CN, ja-JP + * Supported locales: en, zh, ja */ export const ProductsTranslations: TranslationBundle = { en, - 'zh-CN': zhCN, - 'ja-JP': jaJP, + zh, + ja, }; diff --git a/packages/products/src/translations/zh-CN.ts b/packages/products/src/translations/zh.ts similarity index 99% rename from packages/products/src/translations/zh-CN.ts rename to packages/products/src/translations/zh.ts index 434a6459..a5affa04 100644 --- a/packages/products/src/translations/zh-CN.ts +++ b/packages/products/src/translations/zh.ts @@ -1,11 +1,11 @@ import type { TranslationData } from '@objectstack/spec/system'; /** - * 简体中文 (zh-CN) — Products & Pricing Translations + * 简体中文 (zh) — Products & Pricing Translations * * Per-locale file: one file per language, following the `per_locale` convention. */ -export const zhCN: TranslationData = { +export const zh: TranslationData = { objects: { product: { label: '产品', diff --git a/packages/support/__tests__/unit/schemas/i18n-translation.test.ts b/packages/support/__tests__/unit/schemas/i18n-translation.test.ts index a0e17912..c72909ee 100644 --- a/packages/support/__tests__/unit/schemas/i18n-translation.test.ts +++ b/packages/support/__tests__/unit/schemas/i18n-translation.test.ts @@ -13,12 +13,12 @@ describe('Support i18n Translations', () => { it('should contain all supported locales', () => { expect(SupportTranslations).toHaveProperty('en'); - expect(SupportTranslations).toHaveProperty('zh-CN'); - expect(SupportTranslations).toHaveProperty('ja-JP'); + expect(SupportTranslations).toHaveProperty('zh'); + expect(SupportTranslations).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 = SupportTranslations[locale]; expect(data).toBeDefined(); expect(data.objects).toBeDefined(); @@ -35,8 +35,8 @@ describe('Support i18n Translations', () => { it('should have consistent object keys across all locales', () => { const enKeys = Object.keys(SupportTranslations.en.objects ?? {}).sort(); - const zhKeys = Object.keys(SupportTranslations['zh-CN'].objects ?? {}).sort(); - const jaKeys = Object.keys(SupportTranslations['ja-JP'].objects ?? {}).sort(); + const zhKeys = Object.keys(SupportTranslations['zh'].objects ?? {}).sort(); + const jaKeys = Object.keys(SupportTranslations['ja'].objects ?? {}).sort(); expect(enKeys).toEqual(zhKeys); expect(enKeys).toEqual(jaKeys); }); diff --git a/packages/support/src/translations/ja-JP.ts b/packages/support/src/translations/ja.ts similarity index 99% rename from packages/support/src/translations/ja-JP.ts rename to packages/support/src/translations/ja.ts index 6341704d..de792955 100644 --- a/packages/support/src/translations/ja-JP.ts +++ b/packages/support/src/translations/ja.ts @@ -1,14 +1,14 @@ import type { TranslationData } from '@objectstack/spec/system'; /** - * 日本語 (ja-JP) — カスタマーサポート翻訳 + * 日本語 (ja) — カスタマーサポート翻訳 * * Per-locale file: one file per language, following the `per_locale` convention. * Each file exports a single `TranslationData` object for its locale. * * Covers all 23 Support objects with field labels, select options, and help texts. */ -export const jaJP: TranslationData = { +export const ja: TranslationData = { objects: { case: { label: 'ケース', diff --git a/packages/support/src/translations/support.translation.ts b/packages/support/src/translations/support.translation.ts index 3b7e1464..c54757a8 100644 --- a/packages/support/src/translations/support.translation.ts +++ b/packages/support/src/translations/support.translation.ts @@ -1,7 +1,7 @@ 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'; /** * Customer Support — Internationalization (i18n) @@ -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 SupportTranslations: TranslationBundle = { en, - 'zh-CN': zhCN, - 'ja-JP': jaJP, + zh, + ja, }; diff --git a/packages/support/src/translations/zh-CN.ts b/packages/support/src/translations/zh.ts similarity index 99% rename from packages/support/src/translations/zh-CN.ts rename to packages/support/src/translations/zh.ts index 371b510d..fc6765ec 100644 --- a/packages/support/src/translations/zh-CN.ts +++ b/packages/support/src/translations/zh.ts @@ -1,14 +1,14 @@ import type { TranslationData } from '@objectstack/spec/system'; /** - * 简体中文 (zh-CN) — 客户支持翻译 + * 简体中文 (zh) — 客户支持翻译 * * Per-locale file: one file per language, following the `per_locale` convention. * Each file exports a single `TranslationData` object for its locale. * * Covers all 23 Support objects with field labels, select options, and help texts. */ -export const zhCN: TranslationData = { +export const zh: TranslationData = { objects: { case: { label: '工单', From cd44863fc76c136b205700ced63cb8aa221826ff Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Tue, 24 Mar 2026 05:30:03 +0000 Subject: [PATCH 4/4] Fix i18n by merging all plugin translations into a single bundle Previously, all 6 plugin translation bundles were passed as an array with the same namespace 'hotcrm', causing them to overwrite each other. Only the last bundle (HR) was registered, which is why the API only returned HR translations. Now, all translation bundles are deep-merged per locale before being passed to the i18n configuration. This ensures all translations from CRM, Finance, Marketing, Products, Support, and HR are available under the 'hotcrm' namespace. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> Agent-Logs-Url: https://github.com/objectstack-ai/hotcrm/sessions/a22ae23a-82f3-489c-b378-d58b20bee874 --- api/[[...route]].ts | 49 ++++++++++++++++++++++++++++++++++++------- objectstack.config.ts | 49 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/api/[[...route]].ts b/api/[[...route]].ts index fbb1b40d..4fe046cb 100644 --- a/api/[[...route]].ts +++ b/api/[[...route]].ts @@ -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. @@ -484,14 +524,7 @@ async function bootstrap(): Promise { supportedLocales: ['en', 'zh', 'ja'], fallbackLocale: 'en', namespace: 'hotcrm', - translations: [ - CrmTranslations, - FinanceTranslations, - MarketingTranslations, - ProductsTranslations, - SupportTranslations, - HRTranslations, - ], + translations: HotCRMTranslations, }, objects: [], plugins: [], diff --git a/objectstack.config.ts b/objectstack.config.ts index 6e3a9386..f22c0cc1 100644 --- a/objectstack.config.ts +++ b/objectstack.config.ts @@ -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 * @@ -58,14 +98,7 @@ export default defineStack({ supportedLocales: ['en', 'zh', 'ja'], fallbackLocale: 'en', namespace: 'hotcrm', - translations: [ - CrmTranslations, - FinanceTranslations, - MarketingTranslations, - ProductsTranslations, - SupportTranslations, - HRTranslations, - ], + translations: HotCRMTranslations, }, // Empty objects array triggers auto-loading of ObjectQL and the memory driver,