tractatus/scripts/translate-privacy-deepl.js
TheFlow 867d211f4e docs(privacy): add comprehensive Umami Analytics disclosure
GDPR Compliance Update - Added complete section on privacy-first analytics

Changes:
- Updated Section 6 of privacy policy with detailed Umami Analytics information
- Documented what data is collected (page views, referrers, browser, device, country)
- Documented what is NOT collected (IP addresses, personal info, cookies, precise location)
- Added Do Not Track (DNT) support documentation
- Provided opt-out instructions (browser console method, DNT setting)
- Explained cookie-free tracking and EU data storage
- Updated last modified date to October 29, 2025
- Created DeepL translation script for privacy.json
- Translated all new content to German (DE) and French (FR)

Rationale:
- GDPR requires disclosure of all data collection practices
- Umami was deployed in previous session but privacy policy not updated
- This is a mandatory compliance requirement before further work

Testing:
- Verified English HTML updates render correctly
- Confirmed German translation quality (Analytik und Rückverfolgung)
- Validated French translations via DeepL Pro API
- All i18n keys properly mapped

Files Modified:
- public/privacy.html (Section 6 expanded from 13 to 84 lines)
- public/locales/en/privacy.json (added comprehensive section_6 object)
- public/locales/de/privacy.json (DeepL translated section_6)
- public/locales/fr/privacy.json (DeepL translated section_6)
- scripts/translate-privacy-deepl.js (new translation automation script)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 11:30:41 +13:00

137 lines
4.1 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Translate privacy.json from EN to DE and FR using DeepL API
*
* Usage: node scripts/translate-privacy-deepl.js
*
* Requires: DEEPL_API_KEY environment variable
*/
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
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/privacy.json');
const DE_FILE = path.join(__dirname, '../public/locales/de/privacy.json');
const FR_FILE = path.join(__dirname, '../public/locales/fr/privacy.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'
}).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) {
const response = JSON.parse(data);
resolve(response.translations[0].text);
} else {
reject(new Error(`DeepL API error: ${res.statusCode} - ${data}`));
}
});
});
req.on('error', reject);
req.write(postData);
req.end();
});
}
// Recursively translate object
async function translateObject(obj, targetLang) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
result[key] = [];
for (const item of value) {
if (typeof item === 'string') {
console.log(` Translating array item: ${item.substring(0, 50)}...`);
result[key].push(await translateText(item, targetLang));
await new Promise(resolve => setTimeout(resolve, 200)); // Rate limit
} else {
result[key].push(item);
}
}
} else if (typeof value === 'object' && value !== null) {
result[key] = await translateObject(value, targetLang);
} else if (typeof value === 'string') {
console.log(` Translating: ${key}`);
result[key] = await translateText(value, targetLang);
await new Promise(resolve => setTimeout(resolve, 200)); // Rate limit
} else {
result[key] = value;
}
}
return result;
}
async function main() {
try {
console.log('🌍 Starting privacy.json translation with DeepL\n');
// Translate Section 6 only (the new Umami content)
console.log('📝 Translating Section 6 to German (DE)...');
deData.section_6 = await translateObject(enData.section_6, 'DE');
deData.header.last_updated = await translateText(enData.header.last_updated, 'DE');
console.log('\n📝 Translating Section 6 to French (FR)...');
frData.section_6 = await translateObject(enData.section_6, 'FR');
frData.header.last_updated = await translateText(enData.header.last_updated, 'FR');
// Save files
fs.writeFileSync(DE_FILE, JSON.stringify(deData, null, 2) + '\n', 'utf8');
fs.writeFileSync(FR_FILE, JSON.stringify(frData, null, 2) + '\n', 'utf8');
console.log('\n✅ Translation complete!');
console.log(` DE: ${DE_FILE}`);
console.log(` FR: ${FR_FILE}`);
} catch (error) {
console.error('\n❌ Translation failed:', error.message);
process.exit(1);
}
}
main();