import { describe, expect, it } from 'vitest'; import viMessages from '../../messages/vi.json'; import enMessages from '../../messages/en.json'; describe('locale messages', () => { describe('structural parity between vi.json and en.json', () => { function getKeys(obj: Record, prefix = ''): string[] { const keys: string[] = []; for (const key of Object.keys(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key; if ( typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key]) ) { keys.push( ...getKeys(obj[key] as Record, fullKey), ); } else { keys.push(fullKey); } } return keys.sort(); } it('vi.json and en.json have the same top-level namespaces', () => { const viNamespaces = Object.keys(viMessages).sort(); const enNamespaces = Object.keys(enMessages).sort(); expect(viNamespaces).toEqual(enNamespaces); }); it('vi.json and en.json have the exact same key structure', () => { const viKeys = getKeys(viMessages as unknown as Record); const enKeys = getKeys(enMessages as unknown as Record); expect(viKeys).toEqual(enKeys); }); it('no keys are missing from en.json', () => { const viKeys = new Set( getKeys(viMessages as unknown as Record), ); const enKeys = new Set( getKeys(enMessages as unknown as Record), ); const missingInEn = [...viKeys].filter((k) => !enKeys.has(k)); expect(missingInEn).toEqual([]); }); it('no keys are missing from vi.json', () => { const viKeys = new Set( getKeys(viMessages as unknown as Record), ); const enKeys = new Set( getKeys(enMessages as unknown as Record), ); const missingInVi = [...enKeys].filter((k) => !viKeys.has(k)); expect(missingInVi).toEqual([]); }); }); describe('Vietnamese (vi.json) content', () => { it('has a metadata namespace with title', () => { expect(viMessages.metadata).toBeDefined(); expect(viMessages.metadata.title).toBe( 'GoodGo — Nền tảng Bất động sản Việt Nam', ); }); it('has common namespace with Vietnamese translations', () => { expect(viMessages.common.loading).toBe('Đang tải...'); expect(viMessages.common.login).toBe('Đăng nhập'); expect(viMessages.common.register).toBe('Đăng ký'); expect(viMessages.common.logout).toBe('Đăng xuất'); }); it('has nav namespace with Vietnamese navigation labels', () => { expect(viMessages.nav.home).toBe('Trang chủ'); expect(viMessages.nav.search).toBe('Tìm kiếm'); expect(viMessages.nav.pricing).toBe('Bảng giá'); }); it('has propertyTypes namespace with all 9 property types in Vietnamese', () => { const types = viMessages.propertyTypes; expect(Object.keys(types)).toHaveLength(9); expect(types.APARTMENT).toBe('Căn hộ'); expect(types.HOUSE).toBe('Nhà riêng'); expect(types.VILLA).toBe('Biệt thự'); expect(types.LAND).toBe('Đất nền'); expect(types.OFFICE).toBe('Văn phòng'); expect(types.SHOPHOUSE).toBe('Shophouse'); expect(types.ROOM_RENTAL).toBe('Phòng trọ'); expect(types.CONDOTEL).toBe('Condotel'); expect(types.SERVICED_APARTMENT).toBe('Căn hộ dịch vụ'); }); it('has transactionTypes in Vietnamese', () => { expect(viMessages.transactionTypes.SALE).toBe('Bán'); expect(viMessages.transactionTypes.RENT).toBe('Cho thuê'); }); it('has language labels including self-referencing Vietnamese label', () => { expect(viMessages.language.label).toBe('Ngôn ngữ'); expect(viMessages.language.vi).toBe('Tiếng Việt'); expect(viMessages.language.en).toBe('English'); }); it('has auth namespace with login form labels', () => { expect(viMessages.auth.loginTitle).toBe('Đăng nhập'); expect(viMessages.auth.phone).toBe('Số điện thoại'); expect(viMessages.auth.password).toBe('Mật khẩu'); }); it('has pricing namespace with plan tier labels', () => { expect(viMessages.pricing.tiers.FREE).toBe('Miễn phí'); expect(viMessages.pricing.tiers.AGENT_PRO).toBe('Môi giới Pro'); expect(viMessages.pricing.tiers.INVESTOR).toBe('Nhà đầu tư'); expect(viMessages.pricing.tiers.ENTERPRISE).toBe('Doanh nghiệp'); }); it('has search namespace with filter labels', () => { expect(viMessages.search.filters).toBe('Bộ lọc'); expect(viMessages.search.district).toBe('Quận/huyện'); expect(viMessages.search.ward).toBe('Phường/xã'); }); }); describe('English (en.json) content', () => { it('has a metadata namespace with English title', () => { expect(enMessages.metadata.title).toBe( 'GoodGo — Vietnam Real Estate Platform', ); }); it('has common namespace with English translations', () => { expect(enMessages.common.loading).toBe('Loading...'); expect(enMessages.common.login).toBe('Login'); expect(enMessages.common.register).toBe('Register'); expect(enMessages.common.logout).toBe('Logout'); }); it('has propertyTypes in English', () => { expect(enMessages.propertyTypes.APARTMENT).toBe('Apartment'); expect(enMessages.propertyTypes.HOUSE).toBe('House'); expect(enMessages.propertyTypes.VILLA).toBe('Villa'); }); it('has transactionTypes in English', () => { expect(enMessages.transactionTypes.SALE).toBe('Sale'); expect(enMessages.transactionTypes.RENT).toBe('Rent'); }); }); describe('interpolation placeholders', () => { it('vi.json common.errorCode contains {code} placeholder', () => { expect(viMessages.common.errorCode).toContain('{code}'); }); it('en.json common.errorCode contains {code} placeholder', () => { expect(enMessages.common.errorCode).toContain('{code}'); }); it('vi.json common.retriedCount contains {count} placeholder', () => { expect(viMessages.common.retriedCount).toContain('{count}'); }); it('en.json common.retriedCount contains {count} placeholder', () => { expect(enMessages.common.retriedCount).toContain('{count}'); }); it('vi.json search.bedroomsCount contains {count} placeholder', () => { expect(viMessages.search.bedroomsCount).toContain('{count}'); }); it('en.json search.bedroomsCount contains {count} placeholder', () => { expect(enMessages.search.bedroomsCount).toContain('{count}'); }); it('vi.json and en.json pricing.currentPlanBadge both contain {plan}', () => { expect(viMessages.pricing.currentPlanBadge).toContain('{plan}'); expect(enMessages.pricing.currentPlanBadge).toContain('{plan}'); }); }); describe('no empty values', () => { function findEmptyValues( obj: Record, prefix = '', ): string[] { const empty: string[] = []; for (const key of Object.keys(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key; const value = obj[key]; if ( typeof value === 'object' && value !== null && !Array.isArray(value) ) { empty.push( ...findEmptyValues(value as Record, fullKey), ); } else if (typeof value === 'string' && value.trim() === '') { empty.push(fullKey); } } return empty; } it('vi.json has no empty string values', () => { const emptyKeys = findEmptyValues( viMessages as unknown as Record, ); expect(emptyKeys).toEqual([]); }); it('en.json has no empty string values', () => { const emptyKeys = findEmptyValues( enMessages as unknown as Record, ); expect(emptyKeys).toEqual([]); }); }); });