@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
JavaScript
/**
* 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);