UNPKG

@akson/cortex-shopify-translations

Version:

Unified Shopify translations management client with product extraction, translation sync, and CLI tools

131 lines (109 loc) • 4.22 kB
#!/usr/bin/env node /** * Mark completed translations as reviewed after quality check * Allows tracking which translations have been manually verified */ import fs from 'fs/promises'; import path from 'path'; import readline from 'readline'; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function question(prompt) { return new Promise(resolve => rl.question(prompt, resolve)); } async function markReviewed(category, language, interactive = false) { console.log('āœ… Marking translations as reviewed...\n'); const contentDir = path.join(process.cwd(), 'translations', 'content'); const filePath = path.join(contentDir, `${category}.json`); try { const data = JSON.parse(await fs.readFile(filePath, 'utf-8')); let reviewCount = 0; if (interactive) { // Interactive review mode console.log(`šŸ“ Interactive review for ${category} - ${language.toUpperCase()}\n`); for (const translation of data.translations) { if (translation.status[language] === 'completed' && translation[language]) { console.log('─'.repeat(60)); console.log(`Key: ${translation.key}`); console.log(`FR: ${translation.fr}`); console.log(`${language.toUpperCase()}: ${translation[language]}`); if (translation.validation && translation.validation[language]) { const validation = translation.validation[language]; console.log(`\nValidation Score: ${(validation.score * 100).toFixed(0)}%`); if (!validation.checks.glossaryCompliant) { console.log('āš ļø Glossary compliance issue detected'); } if (!validation.checks.noFrenchWords) { console.log('āš ļø Possible French contamination'); } } const answer = await question('\nMark as reviewed? (y/n/q): '); if (answer.toLowerCase() === 'y') { translation.status[language] = 'reviewed'; reviewCount++; } else if (answer.toLowerCase() === 'q') { break; } } } } else { // Bulk review mode - mark all completed as reviewed data.translations.forEach(translation => { if (translation.status[language] === 'completed') { translation.status[language] = 'reviewed'; reviewCount++; } }); } if (reviewCount > 0) { // Update metadata data.metadata.last_updated = new Date().toISOString(); // Recalculate stats const stats = { total: data.translations.length, de: { pending: 0, in_progress: 0, completed: 0, failed: 0, reviewed: 0 }, it: { pending: 0, in_progress: 0, completed: 0, failed: 0, reviewed: 0 }, en: { pending: 0, in_progress: 0, completed: 0, failed: 0, reviewed: 0 } }; data.translations.forEach(t => { ['de', 'it', 'en'].forEach(l => { const status = t.status[l] || 'pending'; if (!stats[l][status]) stats[l][status] = 0; stats[l][status]++; }); }); data.metadata.stats = stats; await fs.writeFile(filePath, JSON.stringify(data, null, 2)); console.log(`\nāœ… Marked ${reviewCount} translations as reviewed`); } else { console.log('No translations marked as reviewed'); } } catch (error) { if (error.code === 'ENOENT') { console.error(`āŒ File not found: ${category}.json`); } else { console.error(`āŒ Error: ${error.message}`); } process.exit(1); } finally { rl.close(); } } // CLI const args = process.argv.slice(2); if (args.length < 2) { console.log('Usage: mark-reviewed.mjs <category> <language> [--interactive]'); console.log('Example: mark-reviewed.mjs checkout de'); console.log('Example: mark-reviewed.mjs myarmy it --interactive'); process.exit(1); } const category = args[0]; const language = args[1]; const interactive = args.includes('--interactive') || args.includes('-i'); if (!['de', 'it', 'en'].includes(language)) { console.error('āŒ Invalid language. Use: de, it, or en'); process.exit(1); } markReviewed(category, language, interactive);