#!/usr/bin/env node /** * Translate gdpr.json from EN to DE and FR using DeepL API * * Usage: node scripts/translate-gdpr-deepl.js [--force] * * Options: * --force Overwrite existing translations * * Requires: DEEPL_API_KEY environment variable */ require('dotenv').config(); const fs = require('fs'); const path = require('path'); const https = require('https'); const DEEPL_API_KEY = process.env.DEEPL_API_KEY; const API_URL = 'api.deepl.com'; // Pro API endpoint const FORCE = process.argv.includes('--force'); if (!DEEPL_API_KEY) { console.error('āŒ ERROR: DEEPL_API_KEY environment variable not set'); console.error(' Set it with: export DEEPL_API_KEY="your-key-here"'); process.exit(1); } const EN_FILE = path.join(__dirname, '../public/locales/en/gdpr.json'); const DE_FILE = path.join(__dirname, '../public/locales/de/gdpr.json'); const FR_FILE = path.join(__dirname, '../public/locales/fr/gdpr.json'); // Load JSON files const enData = JSON.parse(fs.readFileSync(EN_FILE, 'utf8')); const deData = JSON.parse(fs.readFileSync(DE_FILE, 'utf8')); const frData = JSON.parse(fs.readFileSync(FR_FILE, 'utf8')); // DeepL API request function function translateText(text, targetLang) { return new Promise((resolve, reject) => { const postData = new URLSearchParams({ auth_key: DEEPL_API_KEY, text: text, target_lang: targetLang, source_lang: 'EN', formality: 'default', preserve_formatting: '1', tag_handling: 'html' // Preserve HTML tags }).toString(); const options = { hostname: API_URL, port: 443, path: '/v2/translate', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode === 200) { try { const response = JSON.parse(data); resolve(response.translations[0].text); } catch (err) { reject(new Error(`Failed to parse response: ${err.message}`)); } } else { reject(new Error(`DeepL API error: ${res.statusCode} - ${data}`)); } }); }); req.on('error', reject); req.write(postData); req.end(); }); } // Helper to get nested value function getNestedValue(obj, path) { return path.split('.').reduce((current, key) => current?.[key], obj); } // Helper to set nested value function setNestedValue(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); const target = keys.reduce((current, key) => { if (!current[key]) current[key] = {}; return current[key]; }, obj); target[lastKey] = value; } // Recursively find all string values and their paths function findAllStrings(obj, prefix = '') { const strings = []; for (const [key, value] of Object.entries(obj)) { const currentPath = prefix ? `${prefix}.${key}` : key; if (typeof value === 'string') { strings.push(currentPath); } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) { strings.push(...findAllStrings(value, currentPath)); } else if (Array.isArray(value)) { // Handle arrays of strings value.forEach((item, index) => { if (typeof item === 'string') { strings.push(`${currentPath}.${index}`); } }); } } return strings; } // Main translation function async function translateFile(targetLang, targetData, targetFile) { console.log(`\n🌐 Translating to ${targetLang}...`); const allPaths = findAllStrings(enData); let translatedCount = 0; let skippedCount = 0; let errorCount = 0; for (const keyPath of allPaths) { const enValue = getNestedValue(enData, keyPath); const existingValue = getNestedValue(targetData, keyPath); // Skip if already translated (not empty) unless --force flag if (!FORCE && existingValue && existingValue.trim().length > 0 && existingValue !== enValue) { skippedCount++; process.stdout.write('.'); continue; } try { // Translate const translated = await translateText(enValue, targetLang); setNestedValue(targetData, keyPath, translated); translatedCount++; process.stdout.write('āœ“'); // Rate limiting: wait 500ms between requests to avoid 429 errors await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { console.error(`\nāŒ Error translating ${keyPath}:`, error.message); errorCount++; process.stdout.write('āœ—'); } } console.log(`\n\nšŸ“Š Translation Summary for ${targetLang}:`); console.log(` āœ“ Translated: ${translatedCount}`); console.log(` . Skipped (already exists): ${skippedCount}`); console.log(` āœ— Errors: ${errorCount}`); // Save updated file fs.writeFileSync(targetFile, JSON.stringify(targetData, null, 2) + '\n', 'utf8'); console.log(` šŸ’¾ Saved: ${targetFile}`); } // Run translations async function main() { console.log('═══════════════════════════════════════════════════════════'); console.log(' DeepL Translation: gdpr.json (EN → DE, FR)'); console.log('═══════════════════════════════════════════════════════════\n'); if (FORCE) { console.log('āš ļø --force flag enabled: Will overwrite existing translations\n'); } const totalStrings = findAllStrings(enData).length; console.log(`šŸ“ Total translation keys in EN file: ${totalStrings}`); try { // Translate to German await translateFile('DE', deData, DE_FILE); // Translate to French await translateFile('FR', frData, FR_FILE); console.log('\nāœ… Translation complete!'); console.log('\nšŸ’” Next steps:'); console.log(' 1. Review translations in de/gdpr.json and fr/gdpr.json'); console.log(' 2. Test on local server: npm start'); console.log(' 3. Visit http://localhost:9000/gdpr.html and switch languages'); } catch (error) { console.error('\nāŒ Fatal error:', error); process.exit(1); } } main();