Files
reactShop/translate-i18n.js

340 lines
11 KiB
JavaScript
Executable File

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const OpenAI = require('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() {
console.log('🚀 Starting translation process...');
// Check if OpenAI API key is set
if (!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
if (!fs.existsSync(GERMAN_FILE)) {
console.error(`❌ German translation file not found: ${GERMAN_FILE}`);
process.exit(1);
}
try {
// Step 1: Translate German to English
const englishObjectString = await translateToEnglish();
if (englishObjectString) {
// Step 2: Translate English to other languages
await translateToOtherLanguages(englishObjectString);
console.log('🎉 All translations completed successfully!');
} else {
console.error('❌ Failed to create English translation, stopping process');
}
} catch (error) {
console.error('❌ Translation process failed:', error.message);
process.exit(1);
}
}
// Run the script
if (require.main === module) {
main();
}
module.exports = {
translateToEnglish,
translateToOtherLanguages,
readTranslationFile,
writeTranslationFile
};