#!/usr/bin/env node import fs from 'fs'; import path from 'path'; import OpenAI from 'openai'; // Configuration const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const LOCALES_DIR = './src/i18n/locales'; const GERMAN_FILE = path.join(LOCALES_DIR, 'de/translation.js'); const ENGLISH_FILE = path.join(LOCALES_DIR, 'en/translation.js'); // Model configuration const GERMAN_TO_ENGLISH_MODEL = 'gpt-4.1'; // High-quality model for German -> English (critical step) const ENGLISH_TO_OTHER_MODEL = 'gpt-4.1-mini'; // Faster/cheaper model for English -> Other languages // Supported languages for translation const TARGET_LANGUAGES = { 'bg': 'Bulgarian', 'cs': 'Czech', 'es': 'Spanish', 'fr': 'French', 'el': 'Greek', 'hr': 'Croatian', 'hu': 'Hungarian', 'it': 'Italian', 'pl': 'Polish', 'ro': 'Romanian', 'ru': 'Russian', 'sk': 'Slovak', 'sl': 'Slovenian', 'sr': 'Serbian', 'sv': 'Swedish', 'tr': 'Turkish', 'uk': 'Ukrainian', 'ar': 'Arabic (Egyptian)', 'zh': 'Chinese (Simplified)' }; // Initialize OpenAI client const openai = new OpenAI({ apiKey: OPENAI_API_KEY, }); // System prompt for German to English translation const GERMAN_TO_ENGLISH_SYSTEM_PROMPT = ` You MUST translate German strings to English AND add the original German text as a comment after EVERY translated string. CRITICAL REQUIREMENT: Every translated string must have the original German text as a comment. Rules: 1. Translate all German strings to English 2. MANDATORY: Add the original German text as a comment after EVERY translated string using // format 3. Preserve all existing comments from the German version 4. Maintain the exact JavaScript object structure and formatting 5. Keep all interpolation variables like {{count}}, {{vat}}, etc. unchanged 6. Keep locale codes appropriate for English 7. For the locale section, use "en-US" as code 8. Do not translate technical terms that are already in English 9. Preserve any special formatting or HTML entities 10. Return a valid JavaScript object (not JSON) that can be exported MANDATORY FORMAT for every string: "englishTranslation": "English Translation", // Original German Text Examples: "login": "Login", // Anmelden "email": "Email", // E-Mail "password": "Password", // Passwort "home": "Home", // Startseite DO NOT output any string without its German comment. Every single translated string needs the German original as a comment. `; // System prompt template for English to other languages (file content will be inserted) const ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE = ` Translate the English strings in the following file to {{targetLanguage}}, preserving the German comments. Rules: 1. Translate only the English strings to {{targetLanguage}} 2. Keep all German comments unchanged 3. Maintain the exact JavaScript object structure and formatting 4. Keep all interpolation variables like {{count}}, {{vat}}, etc. unchanged 5. Update locale code appropriately for the target language 6. Do not translate technical terms, API keys, or code-related strings 7. Preserve any special formatting or HTML entities 8. Keep existing comments from the English version 9. Return a valid JavaScript object (not JSON) that can be exported Here is the English translation file to translate: {{englishFileContent}} `; // Function to read and parse JavaScript export file function readTranslationFile(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); // Remove the export default and evaluate the object const objectContent = content.replace(/^export default\s*/, '').replace(/;\s*$/, ''); return eval(`(${objectContent})`); } catch (error) { console.error(`Error reading ${filePath}:`, error.message); return null; } } // Function to write translation file (preserving comments as string) function writeTranslationFile(filePath, translationString) { try { // Ensure directory exists const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // Ensure the string has proper export format const content = translationString.startsWith('export default') ? translationString : `export default ${translationString}`; // Ensure it ends with semicolon and newline const finalContent = content.endsWith(';\n') ? content : content.replace(/;?\s*$/, ';\n'); fs.writeFileSync(filePath, finalContent, 'utf8'); console.log(`✅ Successfully wrote ${filePath}`); } catch (error) { console.error(`Error writing ${filePath}:`, error.message); } } // Function to translate content using OpenAI (for German to English) async function translateContent(content, systemPrompt, targetLanguage = null, model = 'gpt-4') { try { const prompt = targetLanguage ? systemPrompt.replace(/{{targetLanguage}}/g, targetLanguage) : systemPrompt; const response = await openai.chat.completions.create({ model: model, messages: [ { role: 'system', content: prompt }, { role: 'user', content: `Please translate this translation file content:\n\n${content}` } ], temperature: 0.1, max_tokens: 4000 }); return response.choices[0].message.content; } catch (error) { console.error('OpenAI API error:', error.message); throw error; } } // Function to translate English to other languages (optimized for caching) async function translateToTargetLanguage(englishContent, targetLanguage, model = 'gpt-4o-mini') { try { // Create system prompt with file content (cacheable) const systemPrompt = ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE .replace(/{{targetLanguage}}/g, targetLanguage) .replace(/{{englishFileContent}}/g, englishContent); const response = await openai.chat.completions.create({ model: model, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: `Please translate to ${targetLanguage}` } ], temperature: 0.1, max_tokens: 4000 }); return response.choices[0].message.content; } catch (error) { console.error('OpenAI API error:', error.message); throw error; } } // Function to extract JavaScript object string from OpenAI response (preserving comments) function extractJSObjectString(response) { try { // Remove code block markers if present let cleaned = response.replace(/```javascript|```json|```/g, '').trim(); // Try to find the object in the response const objectMatch = cleaned.match(/\{[\s\S]*\}/); if (objectMatch) { return objectMatch[0]; } // If no object found, return the cleaned response return cleaned; } catch (error) { console.error('Error parsing OpenAI response:', error.message); console.log('Response was:', response); return null; } } // Main translation function async function translateToEnglish() { console.log(`🔄 Step 1: Translating German to English using ${GERMAN_TO_ENGLISH_MODEL}...`); // Read German translation file const germanContent = fs.readFileSync(GERMAN_FILE, 'utf8'); try { const translatedContent = await translateContent(germanContent, GERMAN_TO_ENGLISH_SYSTEM_PROMPT, null, GERMAN_TO_ENGLISH_MODEL); const englishObjectString = extractJSObjectString(translatedContent); if (englishObjectString) { writeTranslationFile(ENGLISH_FILE, englishObjectString); console.log('✅ German to English translation completed'); return englishObjectString; } else { throw new Error('Failed to parse English translation'); } } catch (error) { console.error('❌ Error translating to English:', error.message); return null; } } // Function to translate English to other languages async function translateToOtherLanguages(englishObjectString) { console.log(`🔄 Step 2: Translating English to other languages using ${ENGLISH_TO_OTHER_MODEL}...`); const englishContent = `export default ${englishObjectString};`; for (const [langCode, langName] of Object.entries(TARGET_LANGUAGES)) { try { console.log(`🔄 Translating to ${langName} (${langCode})...`); const translatedContent = await translateToTargetLanguage( englishContent, langName, ENGLISH_TO_OTHER_MODEL ); const translatedObjectString = extractJSObjectString(translatedContent); if (translatedObjectString) { // Update locale code in the string const updatedString = translatedObjectString.replace( /"code":\s*"[^"]*"/, `"code": "${getLocaleCode(langCode)}"` ); const targetFile = path.join(LOCALES_DIR, `${langCode}/translation.js`); writeTranslationFile(targetFile, updatedString); console.log(`✅ ${langName} translation completed`); } else { console.error(`❌ Failed to parse ${langName} translation`); } // Add delay to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 1000)); } catch (error) { console.error(`❌ Error translating to ${langName}:`, error.message); } } } // Helper function to get locale codes function getLocaleCode(langCode) { const localeCodes = { 'bg': 'bg-BG', 'cs': 'cs-CZ', 'es': 'es-ES', 'fr': 'fr-FR', 'el': 'el-GR', 'hr': 'hr-HR', 'hu': 'hu-HU', 'it': 'it-IT', 'pl': 'pl-PL', 'ro': 'ro-RO', 'ru': 'ru-RU', 'sk': 'sk-SK', 'sl': 'sl-SI', 'sr': 'sr-RS', 'sv': 'sv-SE', 'tr': 'tr-TR', 'uk': 'uk-UA', 'ar': 'ar-EG', 'zh': 'zh-CN' }; return localeCodes[langCode] || `${langCode}-${langCode.toUpperCase()}`; } // Main execution async function main() { // Parse command line arguments const args = process.argv.slice(2); const skipEnglish = args.includes('--skip-english') || args.includes('-s'); const onlyEnglish = args.includes('--only-english') || args.includes('-e'); if (skipEnglish && onlyEnglish) { console.error('❌ Cannot use both --skip-english and --only-english flags'); process.exit(1); } console.log('🚀 Starting translation process...'); if (skipEnglish) { console.log('⏭️ Skipping German → English translation (using existing English file)'); } else if (onlyEnglish) { console.log('🎯 Only translating German → English (skipping other languages)'); } // Check if OpenAI API key is set (only if we're doing actual translation) if (!skipEnglish && !OPENAI_API_KEY) { console.error('❌ OPENAI_API_KEY environment variable is not set'); console.log('Please set your OpenAI API key: export OPENAI_API_KEY="your-api-key-here"'); process.exit(1); } // Check if German file exists (only if we're translating from German) if (!skipEnglish && !fs.existsSync(GERMAN_FILE)) { console.error(`❌ German translation file not found: ${GERMAN_FILE}`); process.exit(1); } try { let englishObjectString; if (skipEnglish) { // Skip German → English, read existing English file if (!fs.existsSync(ENGLISH_FILE)) { console.error(`❌ English translation file not found: ${ENGLISH_FILE}`); console.log('💡 Run without --skip-english first to generate the English file'); process.exit(1); } console.log('📖 Reading existing English translation file...'); const englishContent = fs.readFileSync(ENGLISH_FILE, 'utf8'); // Extract the object part (remove export default and semicolon) englishObjectString = englishContent.replace(/^export default\s*/, '').replace(/;\s*$/, ''); console.log('✅ English file loaded successfully'); } else { // Step 1: Translate German to English englishObjectString = await translateToEnglish(); if (!englishObjectString) { console.error('❌ Failed to create English translation, stopping process'); process.exit(1); } } if (onlyEnglish) { console.log('🎉 English translation completed! Skipping other languages.'); } else { // Step 2: Translate English to other languages await translateToOtherLanguages(englishObjectString); console.log('🎉 All translations completed successfully!'); } } catch (error) { console.error('❌ Translation process failed:', error.message); process.exit(1); } } // Run the script if (import.meta.url === `file://${process.argv[1]}`) { main(); } export { translateToEnglish, translateToOtherLanguages, readTranslationFile, writeTranslationFile };