import { createHash } from 'node:crypto'; import { readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { flatten, LOCALES, type Locale, type Messages } from '../src/index.ts'; export const ROOT = path.resolve(import.meta.dirname, '..'); export const TARGETS = LOCALES.filter((l) => l !== 'ru') as Exclude[]; type Tree = { [k: string]: string | Tree }; export function readLocale(locale: Locale): Messages { return flatten(JSON.parse(readFileSync(path.join(ROOT, 'locales', `${locale}.json`), 'utf8')) as Tree); } export function writeLocale(locale: Locale, flat: Messages, order: string[]) { const tree: Tree = {}; for (const key of order) { if (flat[key] === undefined) continue; const parts = key.split('.'); let node = tree; for (const p of parts.slice(0, -1)) node = (node[p] ??= {}) as Tree; node[parts.at(-1)!] = flat[key]!; } writeFileSync(path.join(ROOT, 'locales', `${locale}.json`), JSON.stringify(tree, null, 2) + '\n'); } /** * review.json хранит для каждого языка { ключ: хеш русского текста на момент проверки }. * Если русский текст изменился, хеш не совпадёт — ключ снова needs_review. */ export type Review = Record, Record>; export const readReview = (): Review => JSON.parse(readFileSync(path.join(ROOT, 'review.json'), 'utf8')); export const writeReview = (r: Review) => writeFileSync(path.join(ROOT, 'review.json'), JSON.stringify(r, null, 2) + '\n'); export const sourceHash = (s: string) => createHash('sha256').update(s).digest('hex').slice(0, 12); export type Status = 'approved' | 'needs_review' | 'missing'; export function statusOf(review: Review, locale: Exclude, key: string, ru: string, value: string | undefined): Status { if (value === undefined || value === '') return 'missing'; return review[locale][key] === sourceHash(ru) ? 'approved' : 'needs_review'; } export const placeholders = (s: string) => [...s.matchAll(/\{\{\s*(\w+)\s*\}\}/g)].map((m) => m[1]).sort().join(','); export function csvEscape(v: string) { return /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v; } export function parseCsv(text: string): string[][] { const rows: string[][] = []; let row: string[] = []; let cell = ''; let quoted = false; for (let i = 0; i < text.length; i++) { const ch = text[i]!; if (quoted) { if (ch === '"' && text[i + 1] === '"') { cell += '"'; i++; } else if (ch === '"') quoted = false; else cell += ch; } else if (ch === '"') quoted = true; else if (ch === ',') { row.push(cell); cell = ''; } else if (ch === '\n' || ch === '\r') { if (ch === '\r' && text[i + 1] === '\n') i++; row.push(cell); rows.push(row); row = []; cell = ''; } else cell += ch; } if (cell || row.length) { row.push(cell); rows.push(row); } return rows.filter((r) => r.some((c) => c !== '')); }