Files
reactShop/translate-i18n.js
sebseb7 905eee57d5 feat(Translation): add kitConfig.js for improved localization in GrowTentKonfigurator
- Updated the translation model by adding kitConfig.js to the list of translation files.
- Enhanced the GrowTentKonfigurator component to utilize translation functions for various UI texts, improving localization support throughout the configuration process.
2025-11-22 09:59:47 +01:00

714 lines
24 KiB
JavaScript
Executable File

#!/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_DIR = path.join(LOCALES_DIR, 'de');
const ENGLISH_DIR = path.join(LOCALES_DIR, 'en');
// Translation file groups
const TRANSLATION_FILES = [
'locale.js',
'navigation.js',
'auth.js',
'cart.js',
'product.js',
'productDialogs.js',
'search.js',
'sorting.js',
'chat.js',
'delivery.js',
'checkout.js',
'payment.js',
'filters.js',
'tax.js',
'footer.js',
'titles.js',
'sections.js',
'pages.js',
'orders.js',
'settings.js',
'common.js',
'kitConfig.js'
];
// Legal document files that need special translation handling
const LEGAL_FILES = [
'legal-agb-delivery.js',
'legal-agb-payment.js',
'legal-agb-consumer.js',
'legal-datenschutz-basic.js',
'legal-datenschutz-customer.js',
'legal-datenschutz-google-orders.js',
'legal-datenschutz-newsletter.js',
'legal-datenschutz-chatbot.js',
'legal-datenschutz-cookies-payment.js',
'legal-datenschutz-rights.js',
'legal-impressum.js',
'legal-widerruf.js',
'legal-batterie.js'
];
// Model configuration
const GERMAN_TO_ENGLISH_MODEL = 'gpt-5.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',
'sq': 'Albanian',
'sr': 'Serbian',
'sv': 'Swedish',
'tr': 'Turkish',
'uk': 'Ukrainian',
'ar': 'Arabic (Egyptian)',
'zh': 'Chinese (Simplified)'
};
/* JTL mapping
bg (Bulgarian) -> kSprache: 37
cs (Czech) -> kSprache: 21
es (Spanish) -> kSprache: 20
fr (French) -> kSprache: 5
el (Greek) -> kSprache: 7
hr (Croatian) -> kSprache: 11
hu (Hungarian) -> kSprache: 24
it (Italian) -> kSprache: 10
pl (Polish) -> kSprache: 14
ro (Romanian) -> kSprache: 26
ru (Russian) -> kSprache: 16
sk (Slovak) -> kSprache: 18
sl (Slovenian) -> kSprache: 19
sq (Albanian) -> kSprache: 190
sr (Serbian) -> kSprache: 93
sv (Swedish) -> kSprache: 17
tr (Turkish) -> kSprache: 22
uk (Ukrainian) -> kSprache: 23
ar (Arabic) -> kSprache: 30
zh (Chinese) -> kSprache: 43
*/
// 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.
`;
// Special system prompt for legal documents (German to English)
const LEGAL_GERMAN_TO_ENGLISH_SYSTEM_PROMPT = `
You MUST translate German legal document strings to English AND add the original German text as a comment after EVERY translated string.
CRITICAL LEGAL DOCUMENT REQUIREMENTS:
- This is a legal document for a GERMAN COMPANY (Growheads) operating under GERMAN LAW
- All legal terms, regulations, and jurisdictions remain GERMAN even when translated
- Company name "Growheads" and German address must remain unchanged
- All references to German laws (DSGVO, BGB, etc.) must remain as German legal references
- German court jurisdiction and German legal framework must be preserved
- The translation is for INFORMATIONAL PURPOSES to help international customers understand the German legal terms
Rules:
1. Translate all German strings to English for comprehension
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 unchanged
6. Keep company name "Growheads" unchanged
7. Keep German address unchanged
8. Keep German law references (DSGVO, BGB, etc.) unchanged
9. Keep German court jurisdiction references unchanged
10. Preserve any special formatting or HTML entities
11. Return a valid JavaScript object (not JSON) that can be exported
MANDATORY FORMAT for every string:
"englishTranslation": "English Translation", // Original German Text
IMPORTANT: The English translation should help international customers understand the German legal terms, but the legal validity remains under German law.
`;
// 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. Drop the comments in output
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. Return a valid JavaScript object (not JSON) that can be exported
Here is the English translation file to translate:
{{englishFileContent}}
`;
// Special system prompt for legal documents (English to other languages)
const LEGAL_ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE = `
Translate the English legal document strings to {{targetLanguage}} while preserving the German legal framework.
CRITICAL LEGAL DOCUMENT REQUIREMENTS:
- This is a legal document for a GERMAN COMPANY (Growheads) operating under GERMAN LAW
- All legal terms, regulations, and jurisdictions remain GERMAN even when translated to {{targetLanguage}}
- Company name "Growheads" and German address must remain unchanged
- All references to German laws (DSGVO, BGB, etc.) must remain as German legal references
- German court jurisdiction and German legal framework must be preserved
- The translation is for INFORMATIONAL PURPOSES to help {{targetLanguage}} speakers understand the German legal terms
Rules:
1. Translate only the English strings to {{targetLanguage}}
2. Drop the comments in output
3. Maintain the exact JavaScript object structure and formatting
4. Keep all interpolation variables unchanged
5. Keep company name "Growheads" unchanged
6. Keep German address unchanged
7. Keep German law references (DSGVO, BGB, etc.) unchanged
8. Keep German court jurisdiction references unchanged
9. Preserve any special formatting or HTML entities
10. Return a valid JavaScript object (not JSON) that can be exported
IMPORTANT: The {{targetLanguage}} translation should help speakers understand the German legal terms, but the legal validity remains under German law.
Here is the English legal document translation file to translate:
{{englishFileContent}}
`;
// Function to check if source file is newer than target file
function isSourceNewer(sourcePath, targetPath) {
try {
// If target doesn't exist, source is considered newer
if (!fs.existsSync(targetPath)) {
return true;
}
const sourceStats = fs.statSync(sourcePath);
const targetStats = fs.statSync(targetPath);
return sourceStats.mtime > targetStats.mtime;
} catch (error) {
console.error(`Error checking file timestamps for ${sourcePath} -> ${targetPath}:`, error.message);
return true; // Default to translating if we can't check
}
}
// Function to get files that need translation from German to English
function getFilesNeedingEnglishTranslation() {
const filesToTranslate = [];
// Check regular translation files
for (const fileName of TRANSLATION_FILES) {
const germanFile = path.join(GERMAN_DIR, fileName);
const englishFile = path.join(ENGLISH_DIR, fileName);
if (!fs.existsSync(germanFile)) {
console.log(`⚠️ German file not found: ${fileName}`);
continue;
}
if (isSourceNewer(germanFile, englishFile)) {
filesToTranslate.push(fileName);
console.log(`📝 ${fileName} needs German → English translation`);
}
}
// Check legal files
for (const fileName of LEGAL_FILES) {
const germanFile = path.join(GERMAN_DIR, fileName);
const englishFile = path.join(ENGLISH_DIR, fileName);
if (!fs.existsSync(germanFile)) {
console.log(`⚠️ German legal file not found: ${fileName}`);
continue;
}
if (isSourceNewer(germanFile, englishFile)) {
filesToTranslate.push(fileName);
console.log(`📋 ${fileName} needs German → English legal translation`);
}
}
return filesToTranslate;
}
// Function to get files that need translation from English to target language
function getFilesNeedingTargetTranslation(langCode) {
const filesToTranslate = [];
const targetDir = path.join(LOCALES_DIR, langCode);
// Check regular translation files
for (const fileName of TRANSLATION_FILES) {
const englishFile = path.join(ENGLISH_DIR, fileName);
const targetFile = path.join(targetDir, fileName);
if (!fs.existsSync(englishFile)) {
console.log(`⚠️ English file not found: ${fileName}`);
continue;
}
if (isSourceNewer(englishFile, targetFile)) {
filesToTranslate.push(fileName);
console.log(`📝 ${fileName} needs English → ${langCode} translation`);
}
}
// Check legal files
for (const fileName of LEGAL_FILES) {
const englishFile = path.join(ENGLISH_DIR, fileName);
const targetFile = path.join(targetDir, fileName);
if (!fs.existsSync(englishFile)) {
console.log(`⚠️ English legal file not found: ${fileName}`);
continue;
}
if (isSourceNewer(englishFile, targetFile)) {
filesToTranslate.push(fileName);
console.log(`📋 ${fileName} needs English → ${langCode} legal translation`);
}
}
return filesToTranslate;
}
// 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-5.1') {
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}` }
],
reasoning_effort: "none",
});
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', isLegalFile = false) {
try {
// Choose appropriate system prompt based on file type
const promptTemplate = isLegalFile
? LEGAL_ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE
: ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE;
// Create system prompt with file content (cacheable)
const systemPrompt = promptTemplate
.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 for multiple files
async function translateToEnglish() {
console.log(`🔄 Step 1: Checking which files need German → English translation...`);
const filesToTranslate = getFilesNeedingEnglishTranslation();
if (filesToTranslate.length === 0) {
console.log('✅ All German → English translations are up to date');
return TRANSLATION_FILES.filter(fileName => fs.existsSync(path.join(ENGLISH_DIR, fileName)));
}
console.log(`🔄 Translating ${filesToTranslate.length} files from German to English using ${GERMAN_TO_ENGLISH_MODEL}...`);
// Ensure English directory exists
if (!fs.existsSync(ENGLISH_DIR)) {
fs.mkdirSync(ENGLISH_DIR, { recursive: true });
}
// Copy structural files (index.js and translation.js) from German directory
copyStructuralFiles(ENGLISH_DIR, 'en');
const translatedFiles = [];
for (const fileName of filesToTranslate) {
const germanFile = path.join(GERMAN_DIR, fileName);
const englishFile = path.join(ENGLISH_DIR, fileName);
console.log(`🔄 Translating ${fileName}...`);
try {
// Read German translation file
const germanContent = fs.readFileSync(germanFile, 'utf8');
// Use special legal prompt for legal documents
const isLegalFile = LEGAL_FILES.includes(fileName);
const systemPrompt = isLegalFile ? LEGAL_GERMAN_TO_ENGLISH_SYSTEM_PROMPT : GERMAN_TO_ENGLISH_SYSTEM_PROMPT;
if (isLegalFile) {
console.log(`📋 Using special legal document translation prompt for ${fileName}`);
}
const translatedContent = await translateContent(germanContent, systemPrompt, null, GERMAN_TO_ENGLISH_MODEL);
const englishObjectString = extractJSObjectString(translatedContent);
if (englishObjectString) {
writeTranslationFile(englishFile, englishObjectString);
translatedFiles.push(fileName);
console.log(`${fileName} translated successfully`);
} else {
throw new Error(`Failed to parse English translation for ${fileName}`);
}
} catch (error) {
console.error(`❌ Error translating ${fileName}:`, error.message);
}
// Add delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log(`✅ German to English translation completed for ${translatedFiles.length} files`);
// Return all English files that exist (both newly translated and existing)
const allFiles = [...TRANSLATION_FILES, ...LEGAL_FILES];
return allFiles.filter(fileName => fs.existsSync(path.join(ENGLISH_DIR, fileName)));
}
// Function to translate English to other languages (multiple files)
async function translateToOtherLanguages(availableEnglishFiles) {
console.log(`🔄 Step 2: Translating English to other languages using ${ENGLISH_TO_OTHER_MODEL}...`);
for (const [langCode, langName] of Object.entries(TARGET_LANGUAGES)) {
console.log(`🔄 Checking ${langName} (${langCode}) translations...`);
// Create target language directory if it doesn't exist
const targetDir = path.join(LOCALES_DIR, langCode);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Copy structural files (index.js and translation.js) from German directory
copyStructuralFiles(targetDir, langCode);
const filesToTranslate = getFilesNeedingTargetTranslation(langCode);
if (filesToTranslate.length === 0) {
console.log(`✅ All English → ${langName} translations are up to date`);
continue;
}
console.log(`🔄 Translating ${filesToTranslate.length} files to ${langName}...`);
for (const fileName of filesToTranslate) {
try {
const englishFile = path.join(ENGLISH_DIR, fileName);
const targetFile = path.join(targetDir, fileName);
console.log(`🔄 Translating ${fileName} to ${langName}...`);
// Read English file
const englishContent = fs.readFileSync(englishFile, 'utf8');
// Check if this is a legal file
const isLegalFile = LEGAL_FILES.includes(fileName);
if (isLegalFile) {
console.log(`📋 Using special legal document translation prompt for ${fileName}${langName}`);
}
const translatedContent = await translateToTargetLanguage(
englishContent,
langName,
ENGLISH_TO_OTHER_MODEL,
isLegalFile
);
const translatedObjectString = extractJSObjectString(translatedContent);
if (translatedObjectString) {
// Special handling for locale.js file
let updatedString = translatedObjectString;
if (fileName === 'locale.js') {
updatedString = translatedObjectString.replace(
/"code":\s*"[^"]*"/,
`"code": "${getLocaleCode(langCode)}"`
);
}
writeTranslationFile(targetFile, updatedString);
console.log(`${fileName} translated to ${langName}`);
} else {
console.error(`❌ Failed to parse ${fileName} translation for ${langName}`);
}
// Add delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`❌ Error translating ${fileName} to ${langName}:`, error.message);
}
}
// Add longer delay between languages
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
// Function to copy index.js and translation.js files from German to target language
function copyStructuralFiles(targetDir, langCode) {
const filesToCopy = ['index.js', 'translation.js'];
for (const fileName of filesToCopy) {
const sourceFile = path.join(GERMAN_DIR, fileName);
const targetFile = path.join(targetDir, fileName);
// Only copy if source exists and target doesn't exist or is older
if (fs.existsSync(sourceFile)) {
if (!fs.existsSync(targetFile) || isSourceNewer(sourceFile, targetFile)) {
try {
fs.copyFileSync(sourceFile, targetFile);
console.log(`📋 Copied ${fileName} to ${langCode} directory`);
} catch (error) {
console.error(`❌ Error copying ${fileName} to ${langCode}:`, error.message);
}
}
} else {
console.warn(`⚠️ Source file not found: ${sourceFile}`);
}
}
}
// 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('Hint: export `cat ../shopApi/.env |grep OPENAI` ; node translate-i18n.js');
process.exit(1);
}
// Check if German directory exists (only if we're translating from German)
if (!skipEnglish && !fs.existsSync(GERMAN_DIR)) {
console.error(`❌ German translation directory not found: ${GERMAN_DIR}`);
process.exit(1);
}
try {
let translatedFiles;
if (skipEnglish) {
// Skip German → English, read existing English files
if (!fs.existsSync(ENGLISH_DIR)) {
console.error(`❌ English translation directory not found: ${ENGLISH_DIR}`);
console.log('💡 Run without --skip-english first to generate the English files');
process.exit(1);
}
console.log('📖 Reading existing English translation files...');
translatedFiles = TRANSLATION_FILES.filter(fileName => {
const englishFile = path.join(ENGLISH_DIR, fileName);
return fs.existsSync(englishFile);
});
console.log(`✅ Found ${translatedFiles.length} English files`);
} else {
// Step 1: Translate German to English
translatedFiles = await translateToEnglish();
if (!translatedFiles || translatedFiles.length === 0) {
console.error('❌ Failed to create English translations, 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(translatedFiles);
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
};