UNPKG

i18ntk

Version:

i18n Tool Kit - Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, auto translation, fixing, reporting, and runtime translation loading.

170 lines (157 loc) 7.32 kB
const path = require('path'); const SecurityUtils = require('../security'); function generateReport(skippedKeys, translatedCount, totalCount, options = {}) { const { sourceFile, targetLang, dryRun = false, timestamp = new Date().toISOString(), placeholderProtected = 0, protectedSkipped = 0, residualUntranslated = [], } = options; const placeholderSkipped = skippedKeys.filter(key => key.skipReason !== 'protected'); const protectedKeys = skippedKeys.filter(key => key.skipReason === 'protected'); const lines = []; lines.push('='.repeat(72)); lines.push(' I18NTK POST-TRANSLATION REPORT'); lines.push('='.repeat(72)); lines.push(` Generated: ${timestamp}`); lines.push(` Source file: ${sourceFile || 'N/A'}`); lines.push(` Target language: ${targetLang || 'N/A'}`); if (dryRun) { lines.push(` Mode: DRY-RUN (no API calls made)`); } lines.push(` Total keys: ${totalCount}`); lines.push(` Translated: ${translatedCount}`); lines.push(` Placeholder-safe: ${String(placeholderProtected).padStart(6)}`); lines.push(` Protected: ${String(protectedSkipped).padStart(6)}`); lines.push(` Skipped: ${skippedKeys.length}`); lines.push(` Leftover warnings: ${String(residualUntranslated.length).padStart(3)}`); lines.push('='.repeat(72)); if (skippedKeys.length === 0 && residualUntranslated.length === 0) { lines.push(''); lines.push(' All strings were processed. No keys were skipped.'); lines.push(''); } else { if (residualUntranslated.length > 0) { lines.push(''); lines.push(' WARNING: The following values still look untranslated after'); lines.push(' Auto Translate and one final retry.'); lines.push(''); lines.push(' A resume report has been written so VS Code companions can'); lines.push(' show the affected keys and retry only unresolved values.'); lines.push(''); lines.push(` ${'-'.repeat(72)}`); lines.push(' File Key Path Current Value'); lines.push(` ${'-'.repeat(72)}`); for (const item of residualUntranslated) { const fileDisplay = String(item.fileName || path.basename(sourceFile || '') || 'N/A').padEnd(20).slice(0, 20); const keyDisplay = String(item.keyPath || '').padEnd(40).slice(0, 40); const valDisplay = String(item.value || '').length > 90 ? String(item.value).slice(0, 87) + '...' : String(item.value || ''); lines.push(` ${fileDisplay} ${keyDisplay} ${valDisplay}`); } lines.push(` ${'-'.repeat(72)}`); } if (placeholderSkipped.length > 0) { lines.push(''); lines.push(' WARNING: The following keys were SKIPPED because they contain'); lines.push(' dynamic placeholder tokens that should be manually translated'); lines.push(' to avoid runtime substitution breakage.'); lines.push(''); lines.push(' These entries were copied verbatim into the output file.'); lines.push(' You MUST manually translate them before using the file.'); } if (protectedKeys.length > 0) { lines.push(''); lines.push(' The following keys were copied unchanged because they matched'); lines.push(' Auto Translate protection rules for keys or exact values.'); } lines.push(''); lines.push(` ${'-'.repeat(64)}`); lines.push(` Key Path Original Value`); lines.push(` ${'-'.repeat(64)}`); for (const skip of skippedKeys) { const keyDisplay = skip.keyPath.length > 50 ? skip.keyPath.substring(0, 47) + '...' : skip.keyPath.padEnd(50); const valDisplay = skip.value.length > 80 ? skip.value.substring(0, 77) + '...' : skip.value; lines.push(` ${keyDisplay} ${valDisplay}`); } lines.push(` ${'-'.repeat(64)}`); if (placeholderSkipped.length > 0) { lines.push(''); lines.push(' REMINDER:'); lines.push(' 1. Open the target JSON file'); lines.push(' 2. Search for the placeholder-skipped keys listed above'); lines.push(' 3. Manually translate each value, preserving all placeholders'); lines.push(' exactly as they appear in the original'); lines.push(' 4. Verify placeholder integrity before runtime use'); lines.push(''); } } lines.push(''); if (residualUntranslated.length > 0) { lines.push(' Auto Translate did not fully complete because leftover'); lines.push(' placeholder-prefixed or English-looking values remain.'); } else if (skippedKeys.length === 0) { lines.push(' The generated file can be used immediately after review.'); lines.push(' Placeholder tokens were preserved automatically where found.'); } else if (placeholderSkipped.length > 0) { lines.push(' The generated file can be used immediately for all'); lines.push(' translated text. Only the skipped keys need'); lines.push(' manual attention.'); } else { lines.push(' Protected keys and values were intentionally copied unchanged.'); lines.push(' Review the output file before using it in production.'); } lines.push('='.repeat(72)); return lines.join('\n'); } function writeReport(reportText, filePath) { if (!filePath) return; try { const resolvedPath = path.resolve(process.cwd(), filePath); SecurityUtils.safeWriteFileSync(resolvedPath, reportText + '\n', path.dirname(resolvedPath), 'utf-8'); } catch (e) { console.error('Failed to write report file:', e.message); } } function writeResidualReport(residualUntranslated, options = {}) { if (!Array.isArray(residualUntranslated) || residualUntranslated.length === 0) return null; const reportDir = path.resolve(process.cwd(), 'i18ntk-reports', 'auto-translate'); const reportPath = path.join(reportDir, options.fileName || 'latest.json'); const timestamp = options.timestamp || new Date().toISOString(); const report = { kind: 'i18ntk.autoTranslateResiduals', version: 1, generatedAt: timestamp, targetLang: options.targetLang || '', sourceFile: options.sourceFile || '', resumeMode: 'onlyMissing', items: residualUntranslated.map((item) => ({ fileName: String(item.fileName || ''), keyPath: String(item.keyPath || ''), value: String(item.value || ''), reason: String(item.reason || 'untranslated') })) }; const ok = SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2) + '\n', process.cwd(), 'utf-8'); return ok ? reportPath : null; } function formatSummaryLine(skippedCount, translatedCount, totalCount, placeholderProtected = 0, protectedSkipped = 0, existingKept = 0) { const protectedPart = placeholderProtected > 0 ? `, ${placeholderProtected} placeholder-safe` : ''; const glossaryPart = protectedSkipped > 0 ? `, ${protectedSkipped} protected` : ''; const existingPart = existingKept > 0 ? `, ${existingKept} existing kept` : ''; return `[translate] ${translatedCount} translated${protectedPart}${glossaryPart}${existingPart}, ${skippedCount} skipped (of ${totalCount} total keys)`; } module.exports = { generateReport, writeReport, writeResidualReport, formatSummaryLine, };