mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 21:26:00 +00:00
Add translation script using OpenAI for missing keys (not finished)
This commit is contained in:
132
common/translations-cli/translate.js
Normal file
132
common/translations-cli/translate.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const OpenAI = require('openai');
|
||||
|
||||
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
|
||||
const translationsDir = path.join(__dirname, '../../translations');
|
||||
const enFilePath = path.join(translationsDir, 'en.json');
|
||||
|
||||
// Language names for OpenAI prompts
|
||||
const languageNames = {
|
||||
'cs.json': 'Czech',
|
||||
'de.json': 'German',
|
||||
'es.json': 'Spanish',
|
||||
'fr.json': 'French',
|
||||
'it.json': 'Italian',
|
||||
'ja.json': 'Japanese',
|
||||
'pt.json': 'Portuguese',
|
||||
'sk.json': 'Slovak',
|
||||
'zh.json': 'Chinese'
|
||||
};
|
||||
|
||||
// Read English (source) translations
|
||||
const enTranslations = JSON.parse(fs.readFileSync(enFilePath, 'utf8'));
|
||||
const enKeys = Object.keys(enTranslations);
|
||||
|
||||
// Get all translation files except en.json
|
||||
const translationFiles = fs.readdirSync(translationsDir)
|
||||
.filter(file => file.endsWith('.json') && file !== 'en.json')
|
||||
.sort();
|
||||
|
||||
console.log(`Found ${enKeys.length} keys in en.json\n`);
|
||||
console.log('='.repeat(80));
|
||||
|
||||
async function translateMissingIds({file, translations, missingIds}){
|
||||
const languageName = languageNames[file];
|
||||
if (!languageName) {
|
||||
console.log(`No language name mapping for file: ${file}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build object with only missing translations
|
||||
const needed = {};
|
||||
missingIds.forEach(key => {
|
||||
needed[key] = enTranslations[key];
|
||||
});
|
||||
|
||||
// Get all existing translations as style examples
|
||||
const existingTranslations = {};
|
||||
Object.keys(translations).forEach(key => {
|
||||
if (translations[key] && !translations[key].startsWith('***')) {
|
||||
existingTranslations[key] = {
|
||||
en: enTranslations[key],
|
||||
translated: translations[key]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const prompt = `You are a professional translator for DbGate, a database management application.
|
||||
|
||||
Translate the following English UI strings to ${languageName}.
|
||||
|
||||
IMPORTANT RULES:
|
||||
1. Preserve ALL placeholders exactly as they appear: {plugin}, {columnNumber}, {0}, {1}, etc.
|
||||
2. Maintain technical terminology appropriately for database software
|
||||
3. Match the translation style, tone, and formality of the existing translations shown below
|
||||
4. Keep the same level of brevity or verbosity as the existing translations
|
||||
5. Return ONLY valid JSON - no markdown, no explanations, no code blocks
|
||||
6. Use the same keys as provided
|
||||
|
||||
EXISTING TRANSLATIONS (for style reference):
|
||||
${JSON.stringify(existingTranslations, null, 2)}
|
||||
|
||||
STRINGS TO TRANSLATE:
|
||||
${JSON.stringify(needed, null, 2)}
|
||||
|
||||
Return format: {"key": "translated value", ...}`;
|
||||
|
||||
const response = await client.chat.completions.create({
|
||||
model: 'gpt-5.1',
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a professional translator specializing in software localization. Match the style and tone of existing translations. Return only valid JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
temperature: 0.2
|
||||
});
|
||||
|
||||
let translatedJson = response.choices[0].message.content.trim();
|
||||
|
||||
// Remove markdown code blocks if present
|
||||
translatedJson = translatedJson.replace(/^```json\n?/, '').replace(/\n?```$/, '');
|
||||
|
||||
return JSON.parse(translatedJson);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
for (const file of translationFiles) {
|
||||
const filePath = path.join(translationsDir, file);
|
||||
const translations = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
|
||||
const missingIds = enKeys.filter(key => !translations.hasOwnProperty(key) || (typeof translations[key] === 'string' && translations[key].startsWith('***')));
|
||||
|
||||
|
||||
console.log(`\n${file.toUpperCase()}`);
|
||||
console.log('-'.repeat(80));
|
||||
|
||||
if (missingIds.length === 0) {
|
||||
console.log('✓ All translations complete!');
|
||||
continue;
|
||||
} else {
|
||||
console.log(`Found ${missingIds.length} untranslated IDs\n`);
|
||||
}
|
||||
|
||||
const newTranslations = await translateMissingIds({file, translations, missingIds});
|
||||
|
||||
if (!newTranslations) {
|
||||
console.log(`Skipping file due to translation error: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(newTranslations)) {
|
||||
translations[key] = value;
|
||||
console.log(`Translated: ${key} => ${value}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, JSON.stringify(translations, null, 2) + '\n', 'utf8');
|
||||
console.log(`\n✓ Updated translations written to ${file}`);
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('Translation complete!\n');
|
||||
})();
|
||||
Reference in New Issue
Block a user