add remote URL translation tab: fetch and translate any website just-in-time
This commit is contained in:
parent
2587fcf050
commit
928f658328
21 changed files with 408 additions and 44 deletions
172
add_remote_url_keys.js
Normal file
172
add_remote_url_keys.js
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Script to add missing remote URL translation keys to all language files
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const languagesDir = './src/languages';
|
||||
|
||||
// The new keys to add in English
|
||||
const newKeys = {
|
||||
remoteUrlTab: "🌐 Remote URL",
|
||||
remoteUrlInfo: "Enter a URL to fetch and translate:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Fetch & Translate"
|
||||
};
|
||||
|
||||
// Translations for each language
|
||||
const translations = {
|
||||
ar: {
|
||||
remoteUrlTab: "🌐 رابط بعيد",
|
||||
remoteUrlInfo: "أدخل رابط لجلبه وترجمته:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 جلب وترجمة"
|
||||
},
|
||||
bn: {
|
||||
remoteUrlTab: "🌐 দূরবর্তী URL",
|
||||
remoteUrlInfo: "একটি URL প্রবেশ করান যা আনতে এবং অনুবাদ করতে:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 আনুন এবং অনুবাদ করুন"
|
||||
},
|
||||
de: {
|
||||
remoteUrlTab: "🌐 Externe URL",
|
||||
remoteUrlInfo: "Geben Sie eine URL ein, um sie abzurufen und zu übersetzen:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Abrufen & Übersetzen"
|
||||
},
|
||||
es: {
|
||||
remoteUrlTab: "🌐 URL Remota",
|
||||
remoteUrlInfo: "Ingresa una URL para obtener y traducir:",
|
||||
remoteUrlPlaceholder: "https://ejemplo.com",
|
||||
fetchAndTranslate: "🌐 Obtener y Traducir"
|
||||
},
|
||||
fr: {
|
||||
remoteUrlTab: "🌐 URL Distante",
|
||||
remoteUrlInfo: "Entrez une URL à récupérer et traduire:",
|
||||
remoteUrlPlaceholder: "https://exemple.com",
|
||||
fetchAndTranslate: "🌐 Récupérer et Traduire"
|
||||
},
|
||||
hi: {
|
||||
remoteUrlTab: "🌐 दूरस्थ URL",
|
||||
remoteUrlInfo: "लाने और अनुवाद करने के लिए एक URL दर्ज करें:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 लाएं और अनुवाद करें"
|
||||
},
|
||||
id: {
|
||||
remoteUrlTab: "🌐 URL Jarak Jauh",
|
||||
remoteUrlInfo: "Masukkan URL untuk diambil dan diterjemahkan:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Ambil & Terjemahkan"
|
||||
},
|
||||
ja: {
|
||||
remoteUrlTab: "🌐 リモートURL",
|
||||
remoteUrlInfo: "取得して翻訳するURLを入力してください:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 取得して翻訳"
|
||||
},
|
||||
ko: {
|
||||
remoteUrlTab: "🌐 원격 URL",
|
||||
remoteUrlInfo: "가져와서 번역할 URL을 입력하세요:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 가져와서 번역"
|
||||
},
|
||||
mr: {
|
||||
remoteUrlTab: "🌐 दूरस्थ URL",
|
||||
remoteUrlInfo: "आणण्यासाठी आणि भाषांतर करण्यासाठी URL प्रविष्ट करा:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 आणा आणि भाषांतर करा"
|
||||
},
|
||||
pt: {
|
||||
remoteUrlTab: "🌐 URL Remota",
|
||||
remoteUrlInfo: "Digite uma URL para buscar e traduzir:",
|
||||
remoteUrlPlaceholder: "https://exemplo.com",
|
||||
fetchAndTranslate: "🌐 Buscar e Traduzir"
|
||||
},
|
||||
ru: {
|
||||
remoteUrlTab: "🌐 Удаленный URL",
|
||||
remoteUrlInfo: "Введите URL для загрузки и перевода:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Загрузить и Перевести"
|
||||
},
|
||||
sw: {
|
||||
remoteUrlTab: "🌐 URL ya Mbali",
|
||||
remoteUrlInfo: "Ingiza URL ya kuleta na kutafsiri:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Leta na Tafsiri"
|
||||
},
|
||||
te: {
|
||||
remoteUrlTab: "🌐 రిమోట్ URL",
|
||||
remoteUrlInfo: "తీసుకురావడానికి మరియు అనువదించడానికి URL ని నమోదు చేయండి:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 తీసుకురండి & అనువదించండి"
|
||||
},
|
||||
tr: {
|
||||
remoteUrlTab: "🌐 Uzak URL",
|
||||
remoteUrlInfo: "Getirmek ve çevirmek için bir URL girin:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Getir ve Çevir"
|
||||
},
|
||||
ur: {
|
||||
remoteUrlTab: "🌐 ریموٹ URL",
|
||||
remoteUrlInfo: "لانے اور ترجمہ کرنے کے لیے URL داخل کریں:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 لائیں اور ترجمہ کریں"
|
||||
},
|
||||
"zh-tw": {
|
||||
remoteUrlTab: "🌐 遠端網址",
|
||||
remoteUrlInfo: "輸入要擷取和翻譯的網址:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 擷取並翻譯"
|
||||
},
|
||||
zh: {
|
||||
remoteUrlTab: "🌐 远程网址",
|
||||
remoteUrlInfo: "输入要获取和翻译的网址:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 获取并翻译"
|
||||
}
|
||||
};
|
||||
|
||||
// Process each language file
|
||||
for (const [langCode, langTranslations] of Object.entries(translations)) {
|
||||
const filePath = path.join(languagesDir, `${langCode}.js`);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Find the position to insert new keys (before the closing })
|
||||
const insertPosition = content.lastIndexOf('};');
|
||||
|
||||
if (insertPosition === -1) {
|
||||
console.error(`Could not find export structure in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the last non-empty line before the closing }
|
||||
const beforeClosing = content.substring(0, insertPosition).trimEnd();
|
||||
|
||||
// Check if any of the new keys already exist
|
||||
const hasNewKeys = Object.keys(langTranslations).some(key =>
|
||||
content.includes(`${key}:`));
|
||||
|
||||
if (hasNewKeys) {
|
||||
console.log(`✅ ${langCode}: Keys already present, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build the new keys string
|
||||
const newKeysString = Object.entries(langTranslations)
|
||||
.map(([key, value]) => `\t${key}: "${value}",`)
|
||||
.join('\n');
|
||||
|
||||
// Insert the new keys
|
||||
const newContent = beforeClosing + ',\n' + newKeysString + '\n};';
|
||||
|
||||
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||
console.log(`✅ ${langCode}: Added remote URL translation keys`);
|
||||
} else {
|
||||
console.error(`File not found: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n🎉 Finished adding remote URL translation keys to all language files!');
|
||||
|
|
@ -106,5 +106,9 @@ export const ar = {
|
|||
copyRawMarkdown: "نسخ النص الخام (Markdown)",
|
||||
copyHTML: "نسخ HTML",
|
||||
aiModalNotAvailable: "نافذة الذكاء الاصطناعي غير متاحة في هذا السياق",
|
||||
useCustomAPI: "استخدم نقطة نهاية مخصصة متوافقة مع OpenAI.",
|
||||
useCustomAPI: "استخدم نقطة نهاية مخصصة متوافقة مع OpenAI.",,
|
||||
remoteUrlTab: "🌐 رابط بعيد",
|
||||
remoteUrlInfo: "أدخل رابط لجلبه وترجمته:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 جلب وترجمة",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const bn = {
|
|||
copyRawMarkdown: "কাঁচা টেক্সট কপি করুন (Markdown)",
|
||||
copyHTML: "HTML কপি করুন",
|
||||
aiModalNotAvailable: "AI মডেল এই প্রসঙ্গে উপলব্ধ নয়",
|
||||
useCustomAPI: "একটি কাস্টম OpenAI সামঞ্জস্যপূর্ণ এন্ডপয়েন্ট ব্যবহার করুন।",
|
||||
useCustomAPI: "একটি কাস্টম OpenAI সামঞ্জস্যপূর্ণ এন্ডপয়েন্ট ব্যবহার করুন।",,
|
||||
remoteUrlTab: "🌐 দূরবর্তী URL",
|
||||
remoteUrlInfo: "একটি URL প্রবেশ করান যা আনতে এবং অনুবাদ করতে:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 আনুন এবং অনুবাদ করুন",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const de = {
|
|||
copyRawMarkdown: "Rohen Text kopieren (Markdown)",
|
||||
copyHTML: "HTML kopieren",
|
||||
aiModalNotAvailable: "KI-Modal ist in diesem Kontext nicht verfügbar",
|
||||
useCustomAPI: "Verwenden Sie einen benutzerdefinierten OpenAI-kompatiblen Endpunkt.",
|
||||
useCustomAPI: "Verwenden Sie einen benutzerdefinierten OpenAI-kompatiblen Endpunkt.",,
|
||||
remoteUrlTab: "🌐 Externe URL",
|
||||
remoteUrlInfo: "Geben Sie eine URL ein, um sie abzurufen und zu übersetzen:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Abrufen & Übersetzen",
|
||||
};
|
||||
|
|
@ -77,6 +77,7 @@ export const en = {
|
|||
// Translation modal tabs
|
||||
translatePageTab: "📄 Translate Page",
|
||||
customTextTab: "✏️ Custom Text",
|
||||
remoteUrlTab: "🌐 Remote URL",
|
||||
|
||||
// TTS modal components
|
||||
speedLabel: "Speed:",
|
||||
|
|
@ -88,6 +89,9 @@ export const en = {
|
|||
translateButton: "🌐 Translate",
|
||||
translatePageButton: "🌐 Translate Page",
|
||||
translateTextButton: "🌐 Translate Text",
|
||||
remoteUrlInfo: "Enter a URL to fetch and translate:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Fetch & Translate",
|
||||
processingText: "Processing...",
|
||||
processingTokens: "🤖 Processing {tokens} tokens...",
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export const es = {
|
|||
// Translation modal tabs
|
||||
translatePageTab: "📄 Traducir Página",
|
||||
customTextTab: "✏️ Texto Personalizado",
|
||||
remoteUrlTab: "🌐 URL Remota",
|
||||
|
||||
// TTS modal components
|
||||
speedLabel: "Velocidad:",
|
||||
|
|
@ -88,6 +89,9 @@ export const es = {
|
|||
translateButton: "🌐 Traducir",
|
||||
translatePageButton: "🌐 Traducir Página",
|
||||
translateTextButton: "🌐 Traducir Texto",
|
||||
remoteUrlInfo: "Ingresa una URL para obtener y traducir:",
|
||||
remoteUrlPlaceholder: "https://ejemplo.com",
|
||||
fetchAndTranslate: "🌐 Obtener y Traducir",
|
||||
processingText: "Procesando...",
|
||||
|
||||
// Button labels for UI
|
||||
|
|
|
|||
|
|
@ -106,5 +106,9 @@ export const fr = {
|
|||
pleaseSelectFile: "Veuillez d'abord sélectionner un fichier !",
|
||||
pleaseEnterTranslateText: "Veuillez entrer du texte à traduire !",
|
||||
aiModalNotAvailable: "Le modal IA n'est pas disponible dans ce contexte",
|
||||
useCustomAPI: "Utiliser un point de terminaison personnalisé compatible OpenAI.",
|
||||
useCustomAPI: "Utiliser un point de terminaison personnalisé compatible OpenAI.",,
|
||||
remoteUrlTab: "🌐 URL Distante",
|
||||
remoteUrlInfo: "Entrez une URL à récupérer et traduire:",
|
||||
remoteUrlPlaceholder: "https://exemple.com",
|
||||
fetchAndTranslate: "🌐 Récupérer et Traduire",
|
||||
};
|
||||
|
|
@ -105,5 +105,9 @@ export const hi = {
|
|||
copyRawMarkdown: "कच्चा टेक्स्ट कॉपी करें (Markdown)",
|
||||
copyHTML: "HTML कॉपी करें",
|
||||
aiModalNotAvailable: "AI मॉडल इस संदर्भ में उपलब्ध नहीं है",
|
||||
useCustomAPI: "एक कस्टम OpenAI संगत एंडपॉइंट का उपयोग करें।",
|
||||
useCustomAPI: "एक कस्टम OpenAI संगत एंडपॉइंट का उपयोग करें।",,
|
||||
remoteUrlTab: "🌐 दूरस्थ URL",
|
||||
remoteUrlInfo: "लाने और अनुवाद करने के लिए एक URL दर्ज करें:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 लाएं और अनुवाद करें",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const id = {
|
|||
copyRawMarkdown: "Salin teks mentah (Markdown)",
|
||||
copyHTML: "Salin HTML",
|
||||
aiModalNotAvailable: "Modal AI tidak tersedia dalam konteks ini",
|
||||
useCustomAPI: "Gunakan endpoint kustom yang kompatibel dengan OpenAI.",
|
||||
useCustomAPI: "Gunakan endpoint kustom yang kompatibel dengan OpenAI.",,
|
||||
remoteUrlTab: "🌐 URL Jarak Jauh",
|
||||
remoteUrlInfo: "Masukkan URL untuk diambil dan diterjemahkan:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Ambil & Terjemahkan",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const ja = {
|
|||
copyRawMarkdown: "生テキストをコピー (Markdown)",
|
||||
copyHTML: "HTMLをコピー",
|
||||
aiModalNotAvailable: "AIモーダルはこのコンテキストでは利用できません",
|
||||
useCustomAPI: "カスタムのOpenAI互換エンドポイントを使用する。",
|
||||
useCustomAPI: "カスタムのOpenAI互換エンドポイントを使用する。",,
|
||||
remoteUrlTab: "🌐 リモートURL",
|
||||
remoteUrlInfo: "取得して翻訳するURLを入力してください:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 取得して翻訳",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const ko = {
|
|||
copyRawMarkdown: "원시 텍스트 복사 (Markdown)",
|
||||
copyHTML: "HTML 복사",
|
||||
aiModalNotAvailable: "AI 모달이 이 컨텍스트에서 사용할 수 없습니다",
|
||||
useCustomAPI: "사용자 정의 OpenAI 호환 엔드포인트를 사용하세요.",
|
||||
useCustomAPI: "사용자 정의 OpenAI 호환 엔드포인트를 사용하세요.",,
|
||||
remoteUrlTab: "🌐 원격 URL",
|
||||
remoteUrlInfo: "가져와서 번역할 URL을 입력하세요:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 가져와서 번역",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const mr = {
|
|||
copyRawMarkdown: "कच्चा मजकूर कॉपी करा (Markdown)",
|
||||
copyHTML: "HTML कॉपी करा",
|
||||
aiModalNotAvailable: "AI मॉडल या संदर्भात उपलब्ध नाही",
|
||||
useCustomAPI: "एक सानुकूल OpenAI सुसंगत एंडपॉइंट वापरा।",
|
||||
useCustomAPI: "एक सानुकूल OpenAI सुसंगत एंडपॉइंट वापरा।",,
|
||||
remoteUrlTab: "🌐 दूरस्थ URL",
|
||||
remoteUrlInfo: "आणण्यासाठी आणि भाषांतर करण्यासाठी URL प्रविष्ट करा:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 आणा आणि भाषांतर करा",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const pt = {
|
|||
copyRawMarkdown: "Copiar texto bruto (Markdown)",
|
||||
copyHTML: "Copiar HTML",
|
||||
aiModalNotAvailable: "Modal de IA não disponível neste contexto",
|
||||
useCustomAPI: "Use um endpoint personalizado compatível com OpenAI.",
|
||||
useCustomAPI: "Use um endpoint personalizado compatível com OpenAI.",,
|
||||
remoteUrlTab: "🌐 URL Remota",
|
||||
remoteUrlInfo: "Digite uma URL para buscar e traduzir:",
|
||||
remoteUrlPlaceholder: "https://exemplo.com",
|
||||
fetchAndTranslate: "🌐 Buscar e Traduzir",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const ru = {
|
|||
copyRawMarkdown: "Копировать исходный текст (Markdown)",
|
||||
copyHTML: "Копировать HTML",
|
||||
aiModalNotAvailable: "Модальное окно ИИ недоступно в этом контексте",
|
||||
useCustomAPI: "Используйте пользовательскую конечную точку, совместимую с OpenAI.",
|
||||
useCustomAPI: "Используйте пользовательскую конечную точку, совместимую с OpenAI.",,
|
||||
remoteUrlTab: "🌐 Удаленный URL",
|
||||
remoteUrlInfo: "Введите URL для загрузки и перевода:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Загрузить и Перевести",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const sw = {
|
|||
copyRawMarkdown: "Nakili maandishi ghafi (Markdown)",
|
||||
copyHTML: "Nakili HTML",
|
||||
aiModalNotAvailable: "Modal ya AI haipatikani katika muktadha huu",
|
||||
useCustomAPI: "Tumia mwisho wa desturi unaofanana na OpenAI.",
|
||||
useCustomAPI: "Tumia mwisho wa desturi unaofanana na OpenAI.",,
|
||||
remoteUrlTab: "🌐 URL ya Mbali",
|
||||
remoteUrlInfo: "Ingiza URL ya kuleta na kutafsiri:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Leta na Tafsiri",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const te = {
|
|||
copyRawMarkdown: "ముడి వచనాన్ని కాపీ చేయండి (Markdown)",
|
||||
copyHTML: "HTML కాపీ చేయండి",
|
||||
aiModalNotAvailable: "AI మోడల్ ఈ సందర్భంలో అందుబాటులో లేదు",
|
||||
useCustomAPI: "కస్టమ్ OpenAI అనుకూల ఎండ్పాయింట్ను ఉపయోగించండి.",
|
||||
useCustomAPI: "కస్టమ్ OpenAI అనుకూల ఎండ్పాయింట్ను ఉపయోగించండి.",,
|
||||
remoteUrlTab: "🌐 రిమోట్ URL",
|
||||
remoteUrlInfo: "తీసుకురావడానికి మరియు అనువదించడానికి URL ని నమోదు చేయండి:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 తీసుకురండి & అనువదించండి",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const tr = {
|
|||
copyRawMarkdown: "Ham metni kopyala (Markdown)",
|
||||
copyHTML: "HTML kopyala",
|
||||
aiModalNotAvailable: "AI modalı bu bağlamda mevcut değil",
|
||||
useCustomAPI: "Özel bir OpenAI uyumlu uç nokta kullanın.",
|
||||
useCustomAPI: "Özel bir OpenAI uyumlu uç nokta kullanın.",,
|
||||
remoteUrlTab: "🌐 Uzak URL",
|
||||
remoteUrlInfo: "Getirmek ve çevirmek için bir URL girin:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Getir ve Çevir",
|
||||
};
|
||||
|
|
@ -106,5 +106,9 @@ export const ur = {
|
|||
copyRawMarkdown: "خام متن کاپی کریں (Markdown)",
|
||||
copyHTML: "HTML کاپی کریں",
|
||||
aiModalNotAvailable: "AI ماڈل اس سیاق میں دستیاب نہیں ہے",
|
||||
useCustomAPI: "ایک کسٹم OpenAI مطابقت پذیر اینڈ پوائنٹ استعمال کریں۔",
|
||||
useCustomAPI: "ایک کسٹم OpenAI مطابقت پذیر اینڈ پوائنٹ استعمال کریں۔",,
|
||||
remoteUrlTab: "🌐 ریموٹ URL",
|
||||
remoteUrlInfo: "لانے اور ترجمہ کرنے کے لیے URL داخل کریں:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 لائیں اور ترجمہ کریں",
|
||||
};
|
||||
|
|
@ -104,5 +104,9 @@ export const zhTw = {
|
|||
copyRawMarkdown: "複製原始文字 (Markdown)",
|
||||
copyHTML: "複製 HTML",
|
||||
aiModalNotAvailable: "AI對話框在此上下文中不可用",
|
||||
useCustomAPI: "使用自訂的OpenAI相容端點。",
|
||||
useCustomAPI: "使用自訂的OpenAI相容端點。",,
|
||||
remoteUrlTab: "🌐 遠端網址",
|
||||
remoteUrlInfo: "輸入要擷取和翻譯的網址:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 擷取並翻譯",
|
||||
};
|
||||
|
|
@ -104,5 +104,9 @@ export const zh = {
|
|||
copyRawMarkdown: "复制原始文本 (Markdown)",
|
||||
copyHTML: "复制 HTML",
|
||||
aiModalNotAvailable: "AI模态框在此上下文中不可用",
|
||||
useCustomAPI: "使用自定义的OpenAI兼容端点。",
|
||||
useCustomAPI: "使用自定义的OpenAI兼容端点。",,
|
||||
remoteUrlTab: "🌐 远程网址",
|
||||
remoteUrlInfo: "输入要获取和翻译的网址:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 获取并翻译",
|
||||
};
|
||||
|
|
@ -4,6 +4,55 @@ import { getUIText } from "./ui-translations.js";
|
|||
import { extractWebpageContent } from "./content.js";
|
||||
import { translateCurrentPage, translateText, SUPPORTED_LANGUAGES } from "./translation.js";
|
||||
|
||||
// Function to fetch and extract content from a remote URL
|
||||
async function fetchRemotePageContent(url) {
|
||||
try {
|
||||
// Use a CORS proxy or fetch API with proper headers
|
||||
// For now, we'll use a simple approach - in production you might want a proper CORS proxy
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch URL: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const htmlContent = await response.text();
|
||||
|
||||
// Create a temporary DOM to extract content
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlContent, 'text/html');
|
||||
|
||||
// Extract meaningful content (similar to extractPageContent)
|
||||
const title = doc.title || '';
|
||||
const metaDescription = doc.querySelector('meta[name="description"]')?.content || '';
|
||||
|
||||
// Remove script and style elements
|
||||
const scripts = doc.querySelectorAll('script, style, noscript');
|
||||
scripts.forEach(el => el.remove());
|
||||
|
||||
// Get text content from body
|
||||
const bodyContent = doc.body ? doc.body.innerText : doc.documentElement.innerText;
|
||||
|
||||
// Combine title, description, and body content
|
||||
const extractedContent = [title, metaDescription, bodyContent]
|
||||
.filter(content => content.trim())
|
||||
.join('\n\n');
|
||||
|
||||
return extractedContent;
|
||||
|
||||
} catch (error) {
|
||||
// If direct fetch fails due to CORS, suggest alternatives
|
||||
if (error.name === 'TypeError' && error.message.includes('CORS')) {
|
||||
throw new Error(`Cannot fetch "${url}" due to CORS restrictions. The website doesn't allow cross-origin requests.`);
|
||||
}
|
||||
throw new Error(`Failed to fetch remote content: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function openTranslateModal() {
|
||||
// Load appropriate CSS based on whether we're using PicoCSS or built-in styles
|
||||
// Detect if PicoCSS is actually present on the page
|
||||
|
|
@ -80,8 +129,20 @@ export function openTranslateModal() {
|
|||
customTabLink.setAttribute("data-i18n", "customTextTab");
|
||||
customTabItem.appendChild(customTabLink);
|
||||
|
||||
// Remote URL tab
|
||||
const remoteTabItem = document.createElement("li");
|
||||
const remoteTabLink = document.createElement("a");
|
||||
remoteTabLink.setAttribute("role", "tab");
|
||||
remoteTabLink.setAttribute("aria-selected", "false");
|
||||
remoteTabLink.setAttribute("aria-controls", "remote-panel");
|
||||
remoteTabLink.href = "#remote-panel";
|
||||
remoteTabLink.textContent = getUIText("remoteUrlTab");
|
||||
remoteTabLink.setAttribute("data-i18n", "remoteUrlTab");
|
||||
remoteTabItem.appendChild(remoteTabLink);
|
||||
|
||||
tabList.appendChild(pageTabItem);
|
||||
tabList.appendChild(customTabItem);
|
||||
tabList.appendChild(remoteTabItem);
|
||||
tabNav.appendChild(tabList);
|
||||
article.appendChild(tabNav);
|
||||
|
||||
|
|
@ -97,6 +158,12 @@ export function openTranslateModal() {
|
|||
customContent.setAttribute("aria-labelledby", "custom-tab");
|
||||
customContent.className = "translate-tab-content";
|
||||
|
||||
const remoteContent = document.createElement("div");
|
||||
remoteContent.id = "remote-panel";
|
||||
remoteContent.setAttribute("role", "tabpanel");
|
||||
remoteContent.setAttribute("aria-labelledby", "remote-tab");
|
||||
remoteContent.className = "translate-tab-content";
|
||||
|
||||
// Page translation content
|
||||
const pageInfo = document.createElement("p");
|
||||
pageInfo.textContent = getUIText("translatePageInfo");
|
||||
|
|
@ -109,8 +176,22 @@ export function openTranslateModal() {
|
|||
textArea.placeholder = getUIText("translatePlaceholder");
|
||||
customContent.appendChild(textArea);
|
||||
|
||||
// Remote URL content
|
||||
const remoteInfo = document.createElement("p");
|
||||
remoteInfo.textContent = getUIText("remoteUrlInfo");
|
||||
remoteInfo.setAttribute("data-i18n", "remoteUrlInfo");
|
||||
remoteContent.appendChild(remoteInfo);
|
||||
|
||||
const urlInput = document.createElement("input");
|
||||
urlInput.type = "url";
|
||||
urlInput.className = "translate-url-input";
|
||||
urlInput.placeholder = getUIText("remoteUrlPlaceholder");
|
||||
urlInput.setAttribute("data-i18n-placeholder", "remoteUrlPlaceholder");
|
||||
remoteContent.appendChild(urlInput);
|
||||
|
||||
article.appendChild(pageContent);
|
||||
article.appendChild(customContent);
|
||||
article.appendChild(remoteContent);
|
||||
|
||||
// Language selection
|
||||
const languageContainer = document.createElement("div");
|
||||
|
|
@ -158,36 +239,49 @@ export function openTranslateModal() {
|
|||
article.appendChild(translateButton);
|
||||
|
||||
// Tab switching using PicoCSS tab navigation
|
||||
let isPageMode = true;
|
||||
let activeTab = "page"; // "page", "custom", or "remote"
|
||||
|
||||
function switchToTab(targetTab) {
|
||||
// Reset all tabs
|
||||
pageTabLink.setAttribute("aria-selected", "false");
|
||||
customTabLink.setAttribute("aria-selected", "false");
|
||||
remoteTabLink.setAttribute("aria-selected", "false");
|
||||
|
||||
pageContent.classList.remove("active");
|
||||
customContent.classList.remove("active");
|
||||
remoteContent.classList.remove("active");
|
||||
|
||||
// Activate target tab
|
||||
if (targetTab === "page") {
|
||||
pageTabLink.setAttribute("aria-selected", "true");
|
||||
pageContent.classList.add("active");
|
||||
translateButton.textContent = getUIText("translatePageButton");
|
||||
} else if (targetTab === "custom") {
|
||||
customTabLink.setAttribute("aria-selected", "true");
|
||||
customContent.classList.add("active");
|
||||
translateButton.textContent = getUIText("translateTextButton");
|
||||
} else if (targetTab === "remote") {
|
||||
remoteTabLink.setAttribute("aria-selected", "true");
|
||||
remoteContent.classList.add("active");
|
||||
translateButton.textContent = getUIText("fetchAndTranslate");
|
||||
}
|
||||
|
||||
activeTab = targetTab;
|
||||
}
|
||||
|
||||
pageTabLink.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
if (!isPageMode) {
|
||||
isPageMode = true;
|
||||
pageTabLink.setAttribute("aria-selected", "true");
|
||||
customTabLink.setAttribute("aria-selected", "false");
|
||||
|
||||
// Visual styles are now handled by CSS classes
|
||||
|
||||
pageContent.classList.add("active");
|
||||
customContent.classList.remove("active");
|
||||
translateButton.textContent = getUIText("translatePageButton");
|
||||
}
|
||||
switchToTab("page");
|
||||
};
|
||||
|
||||
customTabLink.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
if (isPageMode) {
|
||||
isPageMode = false;
|
||||
customTabLink.setAttribute("aria-selected", "true");
|
||||
pageTabLink.setAttribute("aria-selected", "false");
|
||||
|
||||
// Visual styles are now handled by CSS classes
|
||||
|
||||
customContent.classList.add("active");
|
||||
pageContent.classList.remove("active");
|
||||
translateButton.textContent = getUIText("translateTextButton");
|
||||
}
|
||||
switchToTab("custom");
|
||||
};
|
||||
|
||||
remoteTabLink.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
switchToTab("remote");
|
||||
};
|
||||
|
||||
// Translation logic
|
||||
|
|
@ -207,9 +301,19 @@ export function openTranslateModal() {
|
|||
const { getSelectedModel } = await import("./models.js");
|
||||
const { SUPPORTED_LANGUAGES } = await import("./translation.js");
|
||||
|
||||
const rawContent = isPageMode
|
||||
? extractPageContent()
|
||||
: textArea.value.trim();
|
||||
let rawContent;
|
||||
if (activeTab === "page") {
|
||||
rawContent = extractPageContent();
|
||||
} else if (activeTab === "custom") {
|
||||
rawContent = textArea.value.trim();
|
||||
} else if (activeTab === "remote") {
|
||||
const url = urlInput.value.trim();
|
||||
if (!url) {
|
||||
throw new Error("Please enter a URL to translate");
|
||||
}
|
||||
// Fetch and extract content from remote URL
|
||||
rawContent = await fetchRemotePageContent(url);
|
||||
}
|
||||
|
||||
// Limit content length to avoid overwhelming the AI
|
||||
const maxLength = 50000;
|
||||
|
|
@ -350,7 +454,19 @@ ${preservedText}`;
|
|||
|
||||
previewWindow.document.write(htmlWithBase);
|
||||
previewWindow.document.close();
|
||||
previewWindow.document.title = `Translation Preview (${targetLanguageName}) - AI Enabled`;
|
||||
|
||||
// Set title based on translation source
|
||||
let windowTitle;
|
||||
if (activeTab === "remote") {
|
||||
const urlInput = document.querySelector('.translate-url-input');
|
||||
const sourceUrl = urlInput ? urlInput.value.trim() : 'Remote URL';
|
||||
windowTitle = `${sourceUrl} - translated into ${targetLanguageName}`;
|
||||
} else if (activeTab === "custom") {
|
||||
windowTitle = `Custom Text - translated into ${targetLanguageName}`;
|
||||
} else {
|
||||
windowTitle = `${document.title} - translated into ${targetLanguageName}`;
|
||||
}
|
||||
previewWindow.document.title = windowTitle;
|
||||
|
||||
// Add a notice about AI functionality only in preview window (wait for body to be available)
|
||||
const addNotice = () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue