diff --git a/frontend/src/i18n/__tests__/localesNoKeyCollision.spec.ts b/frontend/src/i18n/__tests__/localesNoKeyCollision.spec.ts new file mode 100644 index 0000000000..6e89d10de9 --- /dev/null +++ b/frontend/src/i18n/__tests__/localesNoKeyCollision.spec.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from 'vitest' + +import enAdminAccounts from '../locales/en/admin/accounts' +import enAdminChannels from '../locales/en/admin/channels' +import enAdminOps from '../locales/en/admin/ops' +import enAdminOverview from '../locales/en/admin/overview' +import enAdminResources from '../locales/en/admin/resources' +import enAdminSettings from '../locales/en/admin/settings' +import enCommon from '../locales/en/common' +import enDashboard from '../locales/en/dashboard' +import enLanding from '../locales/en/landing' +import enMisc from '../locales/en/misc' +import zhAdminAccounts from '../locales/zh/admin/accounts' +import zhAdminChannels from '../locales/zh/admin/channels' +import zhAdminOps from '../locales/zh/admin/ops' +import zhAdminOverview from '../locales/zh/admin/overview' +import zhAdminResources from '../locales/zh/admin/resources' +import zhAdminSettings from '../locales/zh/admin/settings' +import zhCommon from '../locales/zh/common' +import zhDashboard from '../locales/zh/dashboard' +import zhLanding from '../locales/zh/landing' +import zhMisc from '../locales/zh/misc' + +// locales/{zh,en}/index.ts 与 admin/index.ts 使用对象展开聚合各域模块, +// 展开模块之间若出现同名顶层键会静默覆盖。本测试将该风险固化为显式失败。 +type Modules = Record> + +function collisions(modules: Modules): string[] { + const seen = new Map() + const out: string[] = [] + for (const [name, mod] of Object.entries(modules)) { + for (const key of Object.keys(mod)) { + const prev = seen.get(key) + if (prev) { + out.push(`"${key}" in both ${prev} and ${name}`) + } else { + seen.set(key, name) + } + } + } + return out +} + +const roots: Record = { + zh: { landing: zhLanding, common: zhCommon, dashboard: zhDashboard, misc: zhMisc }, + en: { landing: enLanding, common: enCommon, dashboard: enDashboard, misc: enMisc } +} + +const admins: Record = { + zh: { + overview: zhAdminOverview, + channels: zhAdminChannels, + accounts: zhAdminAccounts, + resources: zhAdminResources, + ops: zhAdminOps, + settings: zhAdminSettings + }, + en: { + overview: enAdminOverview, + channels: enAdminChannels, + accounts: enAdminAccounts, + resources: enAdminResources, + ops: enAdminOps, + settings: enAdminSettings + } +} + +describe.each(Object.keys(roots))('locale %s spread assembly', (locale) => { + it('root modules have no overlapping top-level keys', () => { + expect(collisions(roots[locale])).toEqual([]) + }) + + it('root modules do not shadow the explicit "admin" namespace', () => { + for (const [name, mod] of Object.entries(roots[locale])) { + expect(Object.keys(mod), `module ${name} must not define "admin"`).not.toContain('admin') + } + }) + + it('admin modules have no overlapping top-level keys', () => { + expect(collisions(admins[locale])).toEqual([]) + }) +})