Skip to content

Instantly share code, notes, and snippets.

@BoDonkey
Created March 12, 2025 10:28
Show Gist options
  • Save BoDonkey/434cd3ff6915b372879d19e6b0747825 to your computer and use it in GitHub Desktop.
Save BoDonkey/434cd3ff6915b372879d19e6b0747825 to your computer and use it in GitHub Desktop.
Node.js process for comparing and translating missing key:string values in two JSON files
const fs = require('fs');
const path = require('path');
const https = require('https');
/**
* Translates text using Google Translate API (free method)
*
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code (e.g., 'es', 'fr', 'de')
* @returns {Promise<string>} - Translated text
*/
async function translateText(text, targetLang) {
return new Promise((resolve, reject) => {
// Encode text for URL
const encodedText = encodeURIComponent(text);
// Google Translate URL
const options = {
hostname: 'translate.googleapis.com',
path: `/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodedText}`,
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0'
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
// Parse response
const json = JSON.parse(data);
const translatedText = json[0][0][0];
resolve(translatedText);
} catch (error) {
reject(new Error(`Translation failed: ${error.message}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
req.end();
});
}
/**
* Gets language code from filename
*
* @param {string} filename - Filename like 'fr.json', 'pt-BR.json' or 'translations-de.json'
* @returns {string} - Language code ('fr', 'pt-BR', 'de', etc.)
*/
function getLanguageCodeFromFilename(filename) {
// Remove file extension
const nameWithoutExt = filename.split('.')[0];
// Extract language code based on common patterns
if (filename.startsWith('messages_')) {
// For messages_fr.json or messages_pt-BR.json pattern
return nameWithoutExt.slice(9);
} else if (nameWithoutExt.includes('-') && !['pt-BR', 'zh-CN', 'zh-TW', 'en-US', 'en-GB', 'fr-CA', 'es-MX'].includes(nameWithoutExt)) {
// For translations-de.json pattern but not pt-BR.json
const parts = nameWithoutExt.split('-');
return parts[parts.length - 1];
} else {
// For fr.json or pt-BR.json pattern
return nameWithoutExt;
}
}
/**
* Synchronizes translation files by finding keys in the English file
* that are missing from other language files and adding them.
*
* @param {string} englishFilePath - Path to the English translation JSON file
* @param {string} languagesDir - Directory containing all translation files
* @param {boolean} keepEnglishValue - Whether to keep English value as placeholder (true) or use empty string (false)
* @param {boolean} autoTranslate - Whether to auto-translate missing strings (true) or not (false)
*/
async function synchronizeTranslations(englishFilePath, languagesDir, keepEnglishValue = true, autoTranslate = false) {
// Read the English file
console.log(`Reading English file: ${englishFilePath}`);
const englishContent = fs.readFileSync(englishFilePath, 'utf8');
const englishData = JSON.parse(englishContent);
// Get all translation files
const files = fs.readdirSync(languagesDir)
.filter(file => file.endsWith('.json') && !file.endsWith('en.json'));
console.log(`Found ${files.length} non-English translation files`);
// Process each non-English file
for (const file of files) {
const filePath = path.join(languagesDir, file);
console.log(`Processing: ${file}`);
// Read the language file
const langContent = fs.readFileSync(filePath, 'utf8');
const langData = JSON.parse(langContent);
// Find missing keys
const missingKeys = [];
let addedCount = 0;
// Get language code from filename
const langCode = getLanguageCodeFromFilename(file);
console.log(`Language code detected: ${langCode}`);
for (const key in englishData) {
if (!langData.hasOwnProperty(key)) {
missingKeys.push(key);
if (autoTranslate) {
try {
// Translate the English value
const englishValue = englishData[key];
console.log(`Translating: "${englishValue}" to ${langCode}`);
// Add delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
const translatedValue = await translateText(englishValue, langCode);
console.log(`Translation result: "${translatedValue}"`);
langData[key] = translatedValue;
} catch (error) {
console.error(`Translation error for key "${key}": ${error.message}`);
// Fallback to English or empty string
langData[key] = keepEnglishValue ? englishData[key] : '';
}
} else {
// Add the missing key with either English value or empty string
langData[key] = keepEnglishValue ? englishData[key] : '';
}
addedCount++;
}
}
if (missingKeys.length > 0) {
console.log(`Found ${missingKeys.length} missing keys in ${file}:`);
missingKeys.forEach(key => {
console.log(` - ${key}: ${englishData[key]}`);
});
// Write the updated language file
fs.writeFileSync(filePath, JSON.stringify(langData, null, 2), 'utf8');
console.log(`Updated ${file} with ${addedCount} new translations`);
} else {
console.log(`No missing keys found in ${file}`);
}
}
console.log('Translation synchronization complete!');
}
// Example usage
// synchronizeTranslations('./locales/en.json', './locales', true, true);
// For command line usage
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length < 2) {
console.log('Usage: node sync-translations.js <english-file-path> <languages-directory> [keep-english-value(true/false)] [auto-translate(true/false)]');
process.exit(1);
}
const englishFilePath = args[0];
const languagesDir = args[1];
const keepEnglishValue = args.length > 2 ? args[2].toLowerCase() === 'true' : true;
const autoTranslate = args.length > 3 ? args[3].toLowerCase() === 'true' : false;
synchronizeTranslations(englishFilePath, languagesDir, keepEnglishValue, autoTranslate)
.catch(error => {
console.error('Error:', error.message);
process.exit(1);
});
}
module.exports = { synchronizeTranslations };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment