import lobbyCatalog from '../../../shared/i18n/lobby.json'; export type LobbyCatalog = typeof lobbyCatalog; export type SupportedLocale = LobbyCatalog['locales']['supported'][number]; export const LOBBY_I18N_CATALOG = lobbyCatalog; export const DEFAULT_LOCALE = lobbyCatalog.locales.default as SupportedLocale; export const SUPPORTED_LOCALES = lobbyCatalog.locales.supported as readonly SupportedLocale[]; export function normalizeLocale(rawLocale?: string | null): SupportedLocale { const locale = (rawLocale ?? '').trim().toLowerCase().replace(/_/g, '-'); if ((SUPPORTED_LOCALES as readonly string[]).includes(locale)) { return locale as SupportedLocale; } const shortLocale = locale.split('-')[0] ?? ''; if ((SUPPORTED_LOCALES as readonly string[]).includes(shortLocale)) { return shortLocale as SupportedLocale; } return DEFAULT_LOCALE; } export function translateCatalogPath( root: Record, keyPath: string, locale: string, fallback = DEFAULT_LOCALE, ): string { const normalizedLocale = normalizeLocale(locale); const segments = keyPath.split('.'); let cursor: unknown = root; for (const segment of segments) { if (!cursor || typeof cursor !== 'object' || !(segment in (cursor as Record))) { return keyPath; } cursor = (cursor as Record)[segment]; } if (!cursor || typeof cursor !== 'object') { return keyPath; } const translations = cursor as Record; return translations[normalizedLocale] ?? translations[fallback] ?? keyPath; } export function collectLocaleParityIssues( node: unknown, locales: readonly string[] = SUPPORTED_LOCALES, path = '', ): string[] { if (!node || typeof node !== 'object') { return []; } const record = node as Record; const keys = Object.keys(record); const isTranslationLeaf = keys.length > 0 && locales.every((locale) => locale in record); if (isTranslationLeaf) { const issues: string[] = []; for (const locale of locales) { const value = record[locale]; if (typeof value !== 'string' || !value.trim()) { issues.push(`${path || ''} missing non-empty '${locale}' translation`); } } return issues; } return keys.flatMap((key) => collectLocaleParityIssues(record[key], locales, path ? `${path}.${key}` : key)); }