Implemented full translation infrastructure for researcher.html: - Added 148 data-i18n attributes across all content sections - Created 142 translation keys in nested JSON structure - Translated all keys to German (DE) and French (FR) via DeepL Pro API - Zero translation errors, all keys validated across 3 languages Content translated includes: - Research Context & Scope (4 major paragraphs) - Theoretical Foundations (Organizational Theory + Values Pluralism accordions) - Empirical Observations (3 documented failure modes with labels) - Six-Component Architecture (all services with descriptions) - Interactive Demonstrations, Resources, Bibliography, Limitations New scripts: - translate-researcher-deepl.js: Automated DeepL translation with rate limiting - validate-researcher-i18n.js: i18n completeness validation tool Translation quality verified with sample checks. Page ready for multilingual deployment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
88 lines
3 KiB
JavaScript
88 lines
3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Validate researcher.html i18n keys against translation files
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Read HTML file and extract data-i18n keys
|
|
const htmlPath = path.join(__dirname, '../public/researcher.html');
|
|
const html = fs.readFileSync(htmlPath, 'utf8');
|
|
|
|
const keyPattern = /data-i18n="([^"]+)"/g;
|
|
const htmlKeys = new Set();
|
|
let match;
|
|
|
|
while ((match = keyPattern.exec(html)) !== null) {
|
|
htmlKeys.add(match[1]);
|
|
}
|
|
|
|
console.log('═══════════════════════════════════════════════════════════');
|
|
console.log(' Researcher.html i18n Validation');
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
|
|
console.log(`📄 Total data-i18n keys in HTML: ${htmlKeys.size}`);
|
|
|
|
// Load translation files
|
|
const enPath = path.join(__dirname, '../public/locales/en/researcher.json');
|
|
const dePath = path.join(__dirname, '../public/locales/de/researcher.json');
|
|
const frPath = path.join(__dirname, '../public/locales/fr/researcher.json');
|
|
|
|
const enData = JSON.parse(fs.readFileSync(enPath, 'utf8'));
|
|
const deData = JSON.parse(fs.readFileSync(dePath, 'utf8'));
|
|
const frData = JSON.parse(fs.readFileSync(frPath, 'utf8'));
|
|
|
|
// Helper to check if nested key exists
|
|
function hasNestedKey(obj, keyPath) {
|
|
const keys = keyPath.split('.');
|
|
let current = obj;
|
|
|
|
for (const key of keys) {
|
|
if (current && typeof current === 'object' && key in current) {
|
|
current = current[key];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return typeof current === 'string' && current.length > 0;
|
|
}
|
|
|
|
// Check each language
|
|
const languages = [
|
|
{ name: 'English (EN)', code: 'en', data: enData },
|
|
{ name: 'German (DE)', code: 'de', data: deData },
|
|
{ name: 'French (FR)', code: 'fr', data: frData }
|
|
];
|
|
|
|
let allValid = true;
|
|
|
|
for (const lang of languages) {
|
|
const missingKeys = [];
|
|
|
|
for (const key of htmlKeys) {
|
|
if (!hasNestedKey(lang.data, key)) {
|
|
missingKeys.push(key);
|
|
}
|
|
}
|
|
|
|
console.log(`\n🌐 ${lang.name}`);
|
|
if (missingKeys.length === 0) {
|
|
console.log(` ✅ All ${htmlKeys.size} keys found`);
|
|
} else {
|
|
console.log(` ❌ Missing ${missingKeys.length} keys:`);
|
|
missingKeys.forEach(key => console.log(` • ${key}`));
|
|
allValid = false;
|
|
}
|
|
}
|
|
|
|
console.log('\n═══════════════════════════════════════════════════════════');
|
|
if (allValid) {
|
|
console.log('✅ VALIDATION PASSED: All i18n keys are properly translated');
|
|
} else {
|
|
console.log('❌ VALIDATION FAILED: Some keys are missing');
|
|
process.exit(1);
|
|
}
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|