From 928f6583281059b340b8fb85b9c5ea97835e3e64 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Fri, 11 Jul 2025 16:44:02 -0400 Subject: [PATCH] add remote URL translation tab: fetch and translate any website just-in-time --- add_remote_url_keys.js | 172 +++++++++++++++++++++++++++++++++++++++++ src/languages/ar.js | 6 +- src/languages/bn.js | 6 +- src/languages/de.js | 6 +- src/languages/en.js | 4 + src/languages/es.js | 4 + src/languages/fr.js | 6 +- src/languages/hi.js | 6 +- src/languages/id.js | 6 +- src/languages/ja.js | 6 +- src/languages/ko.js | 6 +- src/languages/mr.js | 6 +- src/languages/pt.js | 6 +- src/languages/ru.js | 6 +- src/languages/sw.js | 6 +- src/languages/te.js | 6 +- src/languages/tr.js | 6 +- src/languages/ur.js | 6 +- src/languages/zh-tw.js | 6 +- src/languages/zh.js | 6 +- src/translate-modal.js | 170 +++++++++++++++++++++++++++++++++------- 21 files changed, 408 insertions(+), 44 deletions(-) create mode 100644 add_remote_url_keys.js diff --git a/add_remote_url_keys.js b/add_remote_url_keys.js new file mode 100644 index 0000000..211e1da --- /dev/null +++ b/add_remote_url_keys.js @@ -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!'); \ No newline at end of file diff --git a/src/languages/ar.js b/src/languages/ar.js index 45d9548..56c951f 100644 --- a/src/languages/ar.js +++ b/src/languages/ar.js @@ -106,5 +106,9 @@ export const ar = { copyRawMarkdown: "نسخ النص الخام (Markdown)", copyHTML: "نسخ HTML", aiModalNotAvailable: "نافذة الذكاء الاصطناعي غير متاحة في هذا السياق", - useCustomAPI: "استخدم نقطة نهاية مخصصة متوافقة مع OpenAI.", + useCustomAPI: "استخدم نقطة نهاية مخصصة متوافقة مع OpenAI.",, + remoteUrlTab: "🌐 رابط بعيد", + remoteUrlInfo: "أدخل رابط لجلبه وترجمته:", + remoteUrlPlaceholder: "https://example.com", + fetchAndTranslate: "🌐 جلب وترجمة", }; \ No newline at end of file diff --git a/src/languages/bn.js b/src/languages/bn.js index 9046b05..80fd2f0 100644 --- a/src/languages/bn.js +++ b/src/languages/bn.js @@ -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: "🌐 আনুন এবং অনুবাদ করুন", }; \ No newline at end of file diff --git a/src/languages/de.js b/src/languages/de.js index e0e6424..6238757 100644 --- a/src/languages/de.js +++ b/src/languages/de.js @@ -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", }; \ No newline at end of file diff --git a/src/languages/en.js b/src/languages/en.js index 9699aa5..850fc4b 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -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...", diff --git a/src/languages/es.js b/src/languages/es.js index 4bb67c6..56de512 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -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 diff --git a/src/languages/fr.js b/src/languages/fr.js index 89a6f75..b3424ba 100644 --- a/src/languages/fr.js +++ b/src/languages/fr.js @@ -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", }; \ No newline at end of file diff --git a/src/languages/hi.js b/src/languages/hi.js index b335180..4e4de56 100644 --- a/src/languages/hi.js +++ b/src/languages/hi.js @@ -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: "🌐 लाएं और अनुवाद करें", }; \ No newline at end of file diff --git a/src/languages/id.js b/src/languages/id.js index b5dce78..a4fe453 100644 --- a/src/languages/id.js +++ b/src/languages/id.js @@ -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", }; \ No newline at end of file diff --git a/src/languages/ja.js b/src/languages/ja.js index dfec112..5e632df 100644 --- a/src/languages/ja.js +++ b/src/languages/ja.js @@ -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: "🌐 取得して翻訳", }; \ No newline at end of file diff --git a/src/languages/ko.js b/src/languages/ko.js index bfe4dfa..b101c57 100644 --- a/src/languages/ko.js +++ b/src/languages/ko.js @@ -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: "🌐 가져와서 번역", }; \ No newline at end of file diff --git a/src/languages/mr.js b/src/languages/mr.js index bab9e70..4952861 100644 --- a/src/languages/mr.js +++ b/src/languages/mr.js @@ -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: "🌐 आणा आणि भाषांतर करा", }; \ No newline at end of file diff --git a/src/languages/pt.js b/src/languages/pt.js index 2bfe73d..4736ab2 100644 --- a/src/languages/pt.js +++ b/src/languages/pt.js @@ -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", }; \ No newline at end of file diff --git a/src/languages/ru.js b/src/languages/ru.js index 5f1e8cb..0b4be70 100644 --- a/src/languages/ru.js +++ b/src/languages/ru.js @@ -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: "🌐 Загрузить и Перевести", }; \ No newline at end of file diff --git a/src/languages/sw.js b/src/languages/sw.js index ee75a0d..4728c81 100644 --- a/src/languages/sw.js +++ b/src/languages/sw.js @@ -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", }; \ No newline at end of file diff --git a/src/languages/te.js b/src/languages/te.js index a9d1703..0f23115 100644 --- a/src/languages/te.js +++ b/src/languages/te.js @@ -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: "🌐 తీసుకురండి & అనువదించండి", }; \ No newline at end of file diff --git a/src/languages/tr.js b/src/languages/tr.js index b4c4bb0..94199e3 100644 --- a/src/languages/tr.js +++ b/src/languages/tr.js @@ -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", }; \ No newline at end of file diff --git a/src/languages/ur.js b/src/languages/ur.js index e20545a..69f264f 100644 --- a/src/languages/ur.js +++ b/src/languages/ur.js @@ -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: "🌐 لائیں اور ترجمہ کریں", }; \ No newline at end of file diff --git a/src/languages/zh-tw.js b/src/languages/zh-tw.js index 4b16bac..7d67386 100644 --- a/src/languages/zh-tw.js +++ b/src/languages/zh-tw.js @@ -104,5 +104,9 @@ export const zhTw = { copyRawMarkdown: "複製原始文字 (Markdown)", copyHTML: "複製 HTML", aiModalNotAvailable: "AI對話框在此上下文中不可用", - useCustomAPI: "使用自訂的OpenAI相容端點。", + useCustomAPI: "使用自訂的OpenAI相容端點。",, + remoteUrlTab: "🌐 遠端網址", + remoteUrlInfo: "輸入要擷取和翻譯的網址:", + remoteUrlPlaceholder: "https://example.com", + fetchAndTranslate: "🌐 擷取並翻譯", }; \ No newline at end of file diff --git a/src/languages/zh.js b/src/languages/zh.js index 2d26ab3..8f75207 100644 --- a/src/languages/zh.js +++ b/src/languages/zh.js @@ -104,5 +104,9 @@ export const zh = { copyRawMarkdown: "复制原始文本 (Markdown)", copyHTML: "复制 HTML", aiModalNotAvailable: "AI模态框在此上下文中不可用", - useCustomAPI: "使用自定义的OpenAI兼容端点。", + useCustomAPI: "使用自定义的OpenAI兼容端点。",, + remoteUrlTab: "🌐 远程网址", + remoteUrlInfo: "输入要获取和翻译的网址:", + remoteUrlPlaceholder: "https://example.com", + fetchAndTranslate: "🌐 获取并翻译", }; \ No newline at end of file diff --git a/src/translate-modal.js b/src/translate-modal.js index 1247751..d6ff195 100644 --- a/src/translate-modal.js +++ b/src/translate-modal.js @@ -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 = () => {