Added Scripts:
- export-translations.js: Export all translations from MongoDB to JSON
- import-translations.js: Import translations into production database
Purpose:
- Avoid re-running DeepL API on production (saves quota)
- Enable dev-to-prod translation deployment workflow
- Support dry-run and force-overwrite modes
Usage:
- Export: node scripts/export-translations.js /tmp/translations-export.json
- Import: node scripts/import-translations.js /tmp/translations-export.json
Deployment Workflow:
1. Export translations from dev
2. Deploy code to production via deploy.sh
3. Copy export file to production
4. Import translations on production
🌐 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
156 lines
5.1 KiB
JavaScript
156 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Import Translations Script
|
|
*
|
|
* Imports translations from a JSON export file into the database
|
|
* Used to deploy translations from dev to production
|
|
*
|
|
* Usage:
|
|
* node scripts/import-translations.js <input-file>
|
|
*
|
|
* Options:
|
|
* --dry-run Preview import without making changes
|
|
* --force Overwrite existing translations
|
|
*/
|
|
|
|
require('dotenv').config();
|
|
const mongoose = require('mongoose');
|
|
const fs = require('fs');
|
|
|
|
const Document = require('../src/models/Document.model');
|
|
|
|
// Parse arguments
|
|
const args = process.argv.slice(2);
|
|
const options = {
|
|
dryRun: args.includes('--dry-run'),
|
|
force: args.includes('--force'),
|
|
inputFile: args.find(arg => !arg.startsWith('--'))
|
|
};
|
|
|
|
if (!options.inputFile) {
|
|
console.error('❌ ERROR: Input file required');
|
|
console.error('Usage: node scripts/import-translations.js <input-file> [--dry-run] [--force]');
|
|
process.exit(1);
|
|
}
|
|
|
|
async function main() {
|
|
console.log('═══════════════════════════════════════════════════════════');
|
|
console.log(' IMPORT TRANSLATIONS');
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
|
|
if (options.dryRun) {
|
|
console.log('🔍 DRY-RUN MODE - No changes will be made\n');
|
|
}
|
|
|
|
// Load import file
|
|
console.log(`📁 Loading ${options.inputFile}...`);
|
|
if (!fs.existsSync(options.inputFile)) {
|
|
console.error(`❌ ERROR: File not found: ${options.inputFile}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const importData = JSON.parse(fs.readFileSync(options.inputFile, 'utf8'));
|
|
console.log(`✓ Loaded export from ${importData.exported_at}`);
|
|
console.log(`✓ Source: ${importData.source_database}`);
|
|
console.log(`✓ Documents: ${importData.documents.length}`);
|
|
console.log(`✓ Translations: ${importData.total_translations}\n`);
|
|
|
|
// Connect to MongoDB
|
|
console.log('📡 Connecting to MongoDB...');
|
|
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/tractatus_dev', {
|
|
serverSelectionTimeoutMS: 5000
|
|
});
|
|
console.log(`✓ Connected to ${process.env.MONGODB_URI || 'mongodb://localhost:27017/tractatus_dev'}\n`);
|
|
|
|
// Import each document
|
|
const stats = {
|
|
total: importData.documents.length,
|
|
imported: 0,
|
|
skipped: 0,
|
|
failed: 0,
|
|
errors: []
|
|
};
|
|
|
|
console.log('📚 Importing translations...\n');
|
|
|
|
for (const docData of importData.documents) {
|
|
try {
|
|
// Find document by slug (more reliable than _id across environments)
|
|
const doc = await Document.findBySlug(docData.slug);
|
|
|
|
if (!doc) {
|
|
console.log(` ⚠ ${docData.slug}: Document not found, skipping`);
|
|
stats.skipped++;
|
|
continue;
|
|
}
|
|
|
|
// Check if translations already exist
|
|
const hasExisting = doc.translations && Object.keys(doc.translations).length > 0;
|
|
|
|
if (hasExisting && !options.force) {
|
|
console.log(` ⏭ ${docData.slug}: Already has translations (use --force to overwrite)`);
|
|
stats.skipped++;
|
|
continue;
|
|
}
|
|
|
|
if (options.dryRun) {
|
|
const langCount = Object.keys(docData.translations).length;
|
|
console.log(` 🔍 ${docData.slug}: Would import ${langCount} translation(s) ${hasExisting ? '(overwrite)' : '(new)'}`);
|
|
stats.imported++;
|
|
continue;
|
|
}
|
|
|
|
// Import translations
|
|
await Document.update(doc._id.toString(), {
|
|
translations: docData.translations
|
|
});
|
|
|
|
const langCount = Object.keys(docData.translations).length;
|
|
console.log(` ✓ ${docData.slug}: Imported ${langCount} translation(s)`);
|
|
stats.imported++;
|
|
|
|
} catch (error) {
|
|
console.error(` ✗ ${docData.slug}: ${error.message}`);
|
|
stats.failed++;
|
|
stats.errors.push({
|
|
slug: docData.slug,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
console.log('\n═══════════════════════════════════════════════════════════');
|
|
console.log(' IMPORT SUMMARY');
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
|
|
if (options.dryRun) {
|
|
console.log(' Dry run complete - no changes were made\n');
|
|
}
|
|
|
|
console.log(` Total documents: ${stats.total}`);
|
|
console.log(` Imported: ${stats.imported}`);
|
|
console.log(` Skipped: ${stats.skipped}`);
|
|
console.log(` Failed: ${stats.failed}\n`);
|
|
|
|
if (stats.errors.length > 0) {
|
|
console.log(' Errors:');
|
|
stats.errors.forEach(err => {
|
|
console.log(` - ${err.slug}: ${err.error}`);
|
|
});
|
|
console.log('');
|
|
}
|
|
|
|
await mongoose.disconnect();
|
|
console.log('✓ Database disconnected\n');
|
|
|
|
process.exit(stats.failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
// Run
|
|
main().catch(err => {
|
|
console.error('\n❌ Fatal error:', err.message);
|
|
console.error(err.stack);
|
|
process.exit(1);
|
|
});
|