// tests/i18n/locale-parity.test.ts — Plan 01-12 Wave 0 RED unit test. // // Enforces default_locale parity: every key in _locales/ru/messages.json // must exist in _locales/en/messages.json (the default_locale fallback). // Symmetric check for cleanliness; non-empty message strings throughout. // // Polarity at Wave 0 land: RED across the board (no _locales/ yet). // Flips GREEN after Wave 3 Task 1 lands both locale files with parity. // // References: // - RESEARCH Pitfall 4 (missing default_locale key returns empty string, // not the key name — silent UI breakage if EN locale lacks a key the // RU locale defines). import { describe, expect, it } from 'vitest'; import { existsSync, readFileSync } from 'node:fs'; import { resolve as resolvePath } from 'node:path'; const EN_LOCALE_PATH = resolvePath(process.cwd(), '_locales/en/messages.json'); const RU_LOCALE_PATH = resolvePath(process.cwd(), '_locales/ru/messages.json'); interface I18nEntry { message: string; description?: string; } type LocaleFile = Record; function loadLocale(filePath: string): LocaleFile { if (!existsSync(filePath)) { throw new Error(`Missing locale file: ${filePath} (RED until Wave 3 Task 1)`); } return JSON.parse(readFileSync(filePath, 'utf8')); } describe('Plan 01-12: _locales/ key parity (default_locale fallback invariant)', () => { it('every key in _locales/ru/messages.json exists in _locales/en/messages.json', () => { const ru = loadLocale(RU_LOCALE_PATH); const en = loadLocale(EN_LOCALE_PATH); const ruKeys = Object.keys(ru); const missingInEn = ruKeys.filter((k) => !(k in en)); expect( missingInEn, missingInEn.length === 0 ? 'unreachable' : `Keys present in ru/ but missing in en/ (silent UI breakage risk):\n - ${missingInEn.join('\n - ')}`, ).toEqual([]); }); it('every key in _locales/en/messages.json exists in _locales/ru/messages.json (symmetric)', () => { const ru = loadLocale(RU_LOCALE_PATH); const en = loadLocale(EN_LOCALE_PATH); const enKeys = Object.keys(en); const missingInRu = enKeys.filter((k) => !(k in ru)); expect( missingInRu, missingInRu.length === 0 ? 'unreachable' : `Keys present in en/ but missing in ru/ (cleanliness):\n - ${missingInRu.join('\n - ')}`, ).toEqual([]); }); it('every key in _locales/en/messages.json has a non-empty .message string', () => { const en = loadLocale(EN_LOCALE_PATH); for (const k of Object.keys(en)) { expect(typeof en[k].message, `en:${k}.message must be a string`).toBe('string'); expect(en[k].message.length, `en:${k}.message must be non-empty`).toBeGreaterThan(0); } }); it('every key in _locales/ru/messages.json has a non-empty .message string', () => { const ru = loadLocale(RU_LOCALE_PATH); for (const k of Object.keys(ru)) { expect(typeof ru[k].message, `ru:${k}.message must be a string`).toBe('string'); expect(ru[k].message.length, `ru:${k}.message must be non-empty`).toBeGreaterThan(0); } }); });