tractatus/scripts/translate-gdpr-deepl.js
TheFlow ce7747175c feat(compliance): add GDPR compliance page with trilingual support
Implements comprehensive GDPR compliance documentation explaining how the
Tractatus Framework enforces data protection through architectural constraints
rather than policy documents.

Key features:
- 8 sections covering GDPR Articles 5, 6, 15-22, 25, 32, 33
- Framework positioning: BoundaryEnforcer, CrossReferenceValidator, PluralisticDeliberationOrchestrator
- Full trilingual support (EN/DE/FR) via DeepL API (322 translations)
- Footer links and i18n integration across all languages
- Professional translations for legal accuracy

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 10:26:57 +13:00

205 lines
6.3 KiB
JavaScript
Executable file

#!/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();