From 48bb8981defa4961fc3f9d12e251411e033d3320 Mon Sep 17 00:00:00 2001 From: Taiizor <41683699+Taiizor@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:43:55 +0300 Subject: [PATCH 1/3] Localization Support --- src/_locales/en/messages.json | 83 ++++++ src/_locales/tr/messages.json | 83 ++++++ src/content.js | 479 +++++++++++++++++----------------- src/manifest.json | 5 +- src/popup.html | 48 ++-- src/popup.js | 124 ++++++--- 6 files changed, 518 insertions(+), 304 deletions(-) create mode 100644 src/_locales/en/messages.json create mode 100644 src/_locales/tr/messages.json diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json new file mode 100644 index 0000000..4e7cccb --- /dev/null +++ b/src/_locales/en/messages.json @@ -0,0 +1,83 @@ +{ + "extensionName": { + "message": "YouTube Summarizer MVP" + }, + "extensionDescription": { + "message": "MVP: Summarize YouTube videos using ChatGPT 4o mini API." + }, + "settingsTitle": { + "message": "Settings" + }, + "selectLanguage": { + "message": "Select Summary Language" + }, + "languageTurkish": { + "message": "Turkish" + }, + "languageEnglish": { + "message": "English" + }, + "languageSpanish": { + "message": "Spanish" + }, + "languageFrench": { + "message": "French" + }, + "languageGerman": { + "message": "German" + }, + "languageSaved": { + "message": "Preferred language saved!" + }, + "languageSavedNotification": { + "message": "Preferred language saved!" + }, + "languageSavedConsoleLog": { + "message": "Saved language:" + }, + "darkModeEnabled": { + "message": "Dark mode enabled." + }, + "darkModeDisabled": { + "message": "Dark mode disabled." + }, + "closeButtonText": { + "message": "Close" + }, + "copyButtonText": { + "message": "Copy" + }, + "copiedButtonText": { + "message": "Copied!" + }, + "copyErrorLog": { + "message": "Failed to copy text:" + }, + "noVideoIdError": { + "message": "No video ID found. Cannot get transcript." + }, + "noCaptionsError": { + "message": "No captions or transcript found for this video." + }, + "serverError": { + "message": "Error fetching transcript from the server." + }, + "unexpectedError": { + "message": "An unexpected error occurred:" + }, + "noSummaryError": { + "message": "No summary returned from the server." + }, + "connectionError": { + "message": "Failed to connect to the transcript service." + }, + "fetchErrorLog": { + "message": "Error fetching transcript:" + }, + "summarizeButtonText": { + "message": "SUMMARIZE" + }, + "summarizeButtonClicked": { + "message": "Summarize button clicked!" + } +} \ No newline at end of file diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json new file mode 100644 index 0000000..a0bf648 --- /dev/null +++ b/src/_locales/tr/messages.json @@ -0,0 +1,83 @@ +{ + "extensionName": { + "message": "MVP YouTube Özetleyici" + }, + "extensionDescription": { + "message": "MVP: ChatGPT 4o mini API'yi kullanarak YouTube videolarını özetleyin." + }, + "settingsTitle": { + "message": "Ayarlar" + }, + "selectLanguage": { + "message": "Özet Dilini Seçin" + }, + "languageTurkish": { + "message": "Türkçe" + }, + "languageEnglish": { + "message": "İngilizce" + }, + "languageSpanish": { + "message": "İspanyolca" + }, + "languageFrench": { + "message": "Fransızca" + }, + "languageGerman": { + "message": "Almanca" + }, + "languageSaved": { + "message": "Tercih edilen dil kaydedildi!" + }, + "languageSavedNotification": { + "message": "Tercih edilen dil kaydedildi!" + }, + "languageSavedConsoleLog": { + "message": "Kaydedilen dil:" + }, + "darkModeEnabled": { + "message": "Karanlık mod etkinleştirildi." + }, + "darkModeDisabled": { + "message": "Karanlık mod devre dışı bırakıldı." + }, + "closeButtonText": { + "message": "Kapat" + }, + "copyButtonText": { + "message": "Kopyala" + }, + "copiedButtonText": { + "message": "Kopyalandı!" + }, + "copyErrorLog": { + "message": "Metin kopyalanamadı:" + }, + "noVideoIdError": { + "message": "Video kimliği bulunamadı. Transkript alınamıyor." + }, + "noCaptionsError": { + "message": "Bu video için altyazı veya transkript bulunamadı." + }, + "serverError": { + "message": "Sunucudan transkript alınırken hata oluştu." + }, + "unexpectedError": { + "message": "Beklenmedik bir hata oluştu:" + }, + "noSummaryError": { + "message": "Sunucudan özet döndürülmedi." + }, + "connectionError": { + "message": "Transkript hizmetine bağlanılamadı." + }, + "fetchErrorLog": { + "message": "Transkript alınırken hata:" + }, + "summarizeButtonText": { + "message": "ÖZETLE" + }, + "summarizeButtonClicked": { + "message": "Özetle butonuna tıklandı!" + } +} \ No newline at end of file diff --git a/src/content.js b/src/content.js index 4fcd77f..3c7b0f9 100644 --- a/src/content.js +++ b/src/content.js @@ -1,272 +1,281 @@ // Helper function to extract video ID from the URL function getVideoIdFromUrl(url) { - const urlObj = new URL(url); - return urlObj.searchParams.get('v'); + const urlObj = new URL(url); + return urlObj.searchParams.get('v'); } // Simple helper to show messages (using alert for MVP) -function showMessage(msg) { - alert(msg); +function showMessage(msgKey) { + const message = chrome.i18n.getMessage(msgKey) || msgKey; + alert(message); } function displayTranscript(text) { - // Check for user theme preference - const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; - - // Create a modal dialog for better visibility - const modal = document.createElement('div'); - modal.style.position = 'fixed'; - modal.style.left = '50%'; - modal.style.top = '50%'; - modal.style.transform = 'translate(-50%, -50%)'; - modal.style.backgroundColor = isDarkMode ? '#333' : 'white'; - modal.style.color = isDarkMode ? 'white' : 'black'; - modal.style.padding = '20px'; - modal.style.borderRadius = '8px'; - modal.style.boxShadow = isDarkMode - ? '0 4px 6px rgba(255, 255, 255, 0.1)' - : '0 4px 6px rgba(0, 0, 0, 0.1)'; - modal.style.maxWidth = '80%'; - modal.style.maxHeight = '80vh'; - modal.style.overflow = 'auto'; - modal.style.zIndex = '10000'; - - // Add transcript text - const textContent = document.createElement('pre'); - textContent.style.whiteSpace = 'pre-wrap'; - textContent.style.wordBreak = 'break-word'; - textContent.style.margin = '0'; - textContent.style.fontSize = '14px'; - textContent.textContent = text; - - // Add close button - const closeButton = document.createElement('button'); - closeButton.textContent = 'Close'; - closeButton.style.marginTop = '10px'; - closeButton.style.padding = '5px 10px'; - closeButton.style.cursor = 'pointer'; - closeButton.style.backgroundColor = isDarkMode ? '#555' : '#f0f0f0'; - closeButton.style.color = isDarkMode ? 'white' : 'black'; - closeButton.style.border = isDarkMode ? '1px solid #777' : '1px solid #ccc'; - closeButton.style.borderRadius = '4px'; - closeButton.style.marginRight = '10px'; - closeButton.onclick = () => { - overlay.remove(); - modal.remove(); - }; - - // Add copy button - const copyButton = document.createElement('button'); - copyButton.textContent = 'Copy'; - copyButton.style.marginTop = '10px'; - copyButton.style.padding = '5px 10px'; - copyButton.style.cursor = 'pointer'; - copyButton.style.backgroundColor = isDarkMode ? '#555' : '#f0f0f0'; - copyButton.style.color = isDarkMode ? 'white' : 'black'; - copyButton.style.border = isDarkMode ? '1px solid #777' : '1px solid #ccc'; - copyButton.style.borderRadius = '4px'; - copyButton.onclick = async () => { - try { - await navigator.clipboard.writeText(textContent.textContent); - copyButton.textContent = 'Copied!'; - setTimeout(() => { - copyButton.textContent = 'Copy'; - }, 2000); - } catch (err) { - console.error('Failed to copy text:', err); - } - }; - - // Add overlay - const overlay = document.createElement('div'); - overlay.style.position = 'fixed'; - overlay.style.top = '0'; - overlay.style.left = '0'; - overlay.style.width = '100%'; - overlay.style.height = '100%'; - overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; - overlay.style.zIndex = '9999'; - overlay.onclick = () => { - overlay.remove(); - modal.remove(); - }; - - modal.appendChild(textContent); - // Add buttons in a button container to keep them aligned - const buttonContainer = document.createElement('div'); - buttonContainer.style.display = 'flex'; - buttonContainer.style.marginTop = '10px'; - buttonContainer.appendChild(closeButton); - buttonContainer.appendChild(copyButton); - - modal.appendChild(buttonContainer); - - document.body.appendChild(overlay); - document.body.appendChild(modal); + // Check for user theme preference + const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Create a modal dialog for better visibility + const modal = document.createElement('div'); + modal.style.position = 'fixed'; + modal.style.left = '50%'; + modal.style.top = '50%'; + modal.style.transform = 'translate(-50%, -50%)'; + modal.style.backgroundColor = isDarkMode ? '#333' : 'white'; + modal.style.color = isDarkMode ? 'white' : 'black'; + modal.style.padding = '20px'; + modal.style.borderRadius = '8px'; + modal.style.boxShadow = isDarkMode ? + '0 4px 6px rgba(255, 255, 255, 0.1)' : + '0 4px 6px rgba(0, 0, 0, 0.1)'; + modal.style.maxWidth = '80%'; + modal.style.maxHeight = '80vh'; + modal.style.overflow = 'auto'; + modal.style.zIndex = '10000'; + + // Add transcript text + const textContent = document.createElement('pre'); + textContent.style.whiteSpace = 'pre-wrap'; + textContent.style.wordBreak = 'break-word'; + textContent.style.margin = '0'; + textContent.style.fontSize = '14px'; + textContent.textContent = text; + + // Add close button + const closeButton = document.createElement('button'); + closeButton.textContent = chrome.i18n.getMessage("closeButtonText"); + closeButton.style.marginTop = '10px'; + closeButton.style.padding = '5px 10px'; + closeButton.style.cursor = 'pointer'; + closeButton.style.backgroundColor = isDarkMode ? '#555' : '#f0f0f0'; + closeButton.style.color = isDarkMode ? 'white' : 'black'; + closeButton.style.border = isDarkMode ? '1px solid #777' : '1px solid #ccc'; + closeButton.style.borderRadius = '4px'; + closeButton.style.marginRight = '10px'; + closeButton.onclick = () => { + overlay.remove(); + modal.remove(); + }; + + // Add copy button + const copyButton = document.createElement('button'); + copyButton.textContent = 'Copy'; + copyButton.style.marginTop = '10px'; + copyButton.style.padding = '5px 10px'; + copyButton.style.cursor = 'pointer'; + copyButton.style.backgroundColor = isDarkMode ? '#555' : '#f0f0f0'; + copyButton.style.color = isDarkMode ? 'white' : 'black'; + copyButton.style.border = isDarkMode ? '1px solid #777' : '1px solid #ccc'; + copyButton.style.borderRadius = '4px'; + copyButton.onclick = async () => { + try { + await navigator.clipboard.writeText(textContent.textContent); + copyButton.textContent = chrome.i18n.getMessage("copiedButtonText"); + setTimeout(() => { + copyButton.textContent = chrome.i18n.getMessage("copyButtonText"); + }, 2000); + } catch (err) { + console.error(chrome.i18n.getMessage("copyErrorLog"), err); + } + }; + + // Add overlay + const overlay = document.createElement('div'); + overlay.style.position = 'fixed'; + overlay.style.top = '0'; + overlay.style.left = '0'; + overlay.style.width = '100%'; + overlay.style.height = '100%'; + overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + overlay.style.zIndex = '9999'; + overlay.onclick = () => { + overlay.remove(); + modal.remove(); + }; + + modal.appendChild(textContent); + // Add buttons in a button container to keep them aligned + const buttonContainer = document.createElement('div'); + buttonContainer.style.display = 'flex'; + buttonContainer.style.marginTop = '10px'; + buttonContainer.appendChild(closeButton); + buttonContainer.appendChild(copyButton); + + modal.appendChild(buttonContainer); + + document.body.appendChild(overlay); + document.body.appendChild(modal); } // Show Loading Spinner function showLoadingSpinner() { - // Check if spinner already exists - if (document.getElementById('loading-spinner-overlay')) return; + // Check if spinner already exists + if (document.getElementById('loading-spinner-overlay')) return; - const overlay = document.createElement('div'); - overlay.id = 'loading-spinner-overlay'; + const overlay = document.createElement('div'); + overlay.id = 'loading-spinner-overlay'; - const spinner = document.createElement('div'); - spinner.id = 'loading-spinner'; + const spinner = document.createElement('div'); + spinner.id = 'loading-spinner'; - overlay.appendChild(spinner); - document.body.appendChild(overlay); + overlay.appendChild(spinner); + document.body.appendChild(overlay); - // Display the spinner - overlay.style.display = 'flex'; + // Display the spinner + overlay.style.display = 'flex'; } // Hide Loading Spinner function hideLoadingSpinner() { - const overlay = document.getElementById('loading-spinner-overlay'); - if (overlay) { - overlay.remove(); - } + const overlay = document.getElementById('loading-spinner-overlay'); + if (overlay) { + overlay.remove(); + } } // Handle Summarize Button Click async function handleSummarizeButtonClick() { - const videoId = getVideoIdFromUrl(window.location.href); - if (!videoId) { - showMessage("No video ID found. Cannot get transcript."); - return; - } - - // Retrieve the user's preferred language - const preferredLanguage = await new Promise((resolve) => { - chrome.storage.sync.get(['preferredLanguage'], (result) => { - // Default to 'tr' if not set - resolve(result.preferredLanguage || 'tr'); - }); - }); - - try { - showLoadingSpinner(); - - const response = await fetch(`http://localhost:8000/transcript?video_id=${videoId}&summary_language=${preferredLanguage}`, { - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - mode: 'cors' - }); - - if (!response.ok) { - const errorText = await response.text(); - if (response.status === 404) { - showMessage("No captions or transcript found for this video."); - } else if (response.status === 500) { - showMessage("Error fetching transcript from the server."); - } else { - showMessage("An unexpected error occurred: " + errorText); - } - return; - } - - const data = await response.json(); - if (data && data.summary) { - displayTranscript(data.summary); - } else { - showMessage("No summary returned from the server."); - } - } catch (error) { - console.error("Error fetching transcript:", error); - showMessage("Failed to connect to the transcript service."); - } finally { - hideLoadingSpinner(); - } + const videoId = getVideoIdFromUrl(window.location.href); + if (!videoId) { + showMessage("No video ID found. Cannot get transcript."); + return; + } + + // Retrieve the user's preferred language + const preferredLanguage = await new Promise((resolve) => { + chrome.storage.sync.get(['preferredLanguage'], (result) => { + // Default to 'tr' if not set + resolve(result.preferredLanguage || 'tr'); + }); + }); + + try { + showLoadingSpinner(); + + const response = await fetch(`http://localhost:8000/transcript?video_id=${videoId}&summary_language=${preferredLanguage}`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + mode: 'cors' + }); + + if (!response.ok) { + const errorText = await response.text(); + if (response.status === 404) { + showMessage("noCaptionsError"); + } else if (response.status === 500) { + showMessage("serverError"); + } else { + showMessage("unexpectedError"); + } + return; + } + + const data = await response.json(); + if (data && data.summary) { + displayTranscript(data.summary); + } else { + showMessage("noSummaryError"); + } + } catch (error) { + console.error(chrome.i18n.getMessage("fetchErrorLog"), error); + showMessage("connectionError"); + } finally { + hideLoadingSpinner(); + } } function addSummarizeButtonIfNeeded() { - // Try the selector related to the Subscribe button - const subscribeBtnDiv = document.querySelector("#subscribe-button > ytd-button-renderer > yt-button-shape > a > yt-touch-feedback-shape > div"); - - if (subscribeBtnDiv && !document.querySelector('#mySummarizeButton')) { - const summarizeBtn = document.createElement('button'); - summarizeBtn.id = 'mySummarizeButton'; - summarizeBtn.textContent = 'SUMMARIZE'; - - summarizeBtn.style.display = 'inline-flex'; - summarizeBtn.style.alignItems = 'center'; - summarizeBtn.style.justifyContent = 'center'; - summarizeBtn.style.padding = '6px 12px'; - summarizeBtn.style.marginLeft = '12px'; - summarizeBtn.style.backgroundColor = '#ff5757'; - summarizeBtn.style.color = '#ffffff'; - summarizeBtn.style.border = 'none'; - summarizeBtn.style.borderRadius = '4px'; - summarizeBtn.style.cursor = 'pointer'; - summarizeBtn.style.fontWeight = '600'; - summarizeBtn.style.fontSize = '14px'; - summarizeBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)'; - - summarizeBtn.addEventListener('mouseenter', () => { - summarizeBtn.style.backgroundColor = '#e64646'; - }); - summarizeBtn.addEventListener('mouseleave', () => { - summarizeBtn.style.backgroundColor = '#ff5757'; - }); - - subscribeBtnDiv.insertAdjacentElement('afterend', summarizeBtn); - - summarizeBtn.addEventListener('click', async () => { - console.log('Summarize button clicked!'); - await handleSummarizeButtonClick(); - }); - } else if (!subscribeBtnDiv && !document.querySelector('#mySummarizeButton')) { - // Fallback in case subscribe button is not found - const actionButtonsContainer = document.querySelector('#top-level-buttons-computed'); - if (actionButtonsContainer && !document.querySelector('#mySummarizeButton')) { - const summarizeBtn = document.createElement('button'); - summarizeBtn.id = 'mySummarizeButton'; - summarizeBtn.textContent = 'SUMMARIZE'; - - summarizeBtn.style.display = 'inline-flex'; - summarizeBtn.style.alignItems = 'center'; - summarizeBtn.style.justifyContent = 'center'; - summarizeBtn.style.padding = '6px 12px'; - summarizeBtn.style.marginLeft = '12px'; - summarizeBtn.style.backgroundColor = '#ff5757'; - summarizeBtn.style.color = '#ffffff'; - summarizeBtn.style.border = 'none'; - summarizeBtn.style.borderRadius = '4px'; - summarizeBtn.style.cursor = 'pointer'; - summarizeBtn.style.fontWeight = '600'; - summarizeBtn.style.fontSize = '14px'; - summarizeBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)'; - - summarizeBtn.addEventListener('mouseenter', () => { - summarizeBtn.style.backgroundColor = '#e64646'; - }); - summarizeBtn.addEventListener('mouseleave', () => { - summarizeBtn.style.backgroundColor = '#ff5757'; - }); - - actionButtonsContainer.appendChild(summarizeBtn); - - summarizeBtn.addEventListener('click', async () => { - console.log('Summarize button clicked!'); - await handleSummarizeButtonClick(); - }); - } - } + // Try the selector related to the Subscribe button + const subscribeBtnDiv = document.querySelector("#subscribe-button > ytd-button-renderer > yt-button-shape > a > yt-touch-feedback-shape > div"); + + if (subscribeBtnDiv && !document.querySelector('#mySummarizeButton')) { + const summarizeBtn = document.createElement('button'); + summarizeBtn.id = 'mySummarizeButton'; + summarizeBtn.textContent = chrome.i18n.getMessage("summarizeButtonText"); + + summarizeBtn.style.display = 'inline-flex'; + summarizeBtn.style.alignItems = 'center'; + summarizeBtn.style.justifyContent = 'center'; + summarizeBtn.style.padding = '6px 12px'; + summarizeBtn.style.marginLeft = '12px'; + summarizeBtn.style.backgroundColor = '#ff5757'; + summarizeBtn.style.color = '#ffffff'; + summarizeBtn.style.border = 'none'; + summarizeBtn.style.borderRadius = '4px'; + summarizeBtn.style.cursor = 'pointer'; + summarizeBtn.style.fontWeight = '600'; + summarizeBtn.style.fontSize = '14px'; + summarizeBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)'; + + summarizeBtn.addEventListener('mouseenter', () => { + summarizeBtn.style.backgroundColor = '#e64646'; + }); + summarizeBtn.addEventListener('mouseleave', () => { + summarizeBtn.style.backgroundColor = '#ff5757'; + }); + + subscribeBtnDiv.insertAdjacentElement('afterend', summarizeBtn); + + summarizeBtn.addEventListener('click', async () => { + console.log(chrome.i18n.getMessage("summarizeButtonClicked")); + await handleSummarizeButtonClick(); + }); + } else if (!subscribeBtnDiv && !document.querySelector('#mySummarizeButton')) { + // Fallback in case subscribe button is not found + const actionButtonsContainer = document.querySelector('#top-level-buttons-computed'); + if (actionButtonsContainer && !document.querySelector('#mySummarizeButton')) { + const summarizeBtn = document.createElement('button'); + summarizeBtn.id = 'mySummarizeButton'; + summarizeBtn.textContent = chrome.i18n.getMessage("summarizeButtonText"); + + summarizeBtn.style.display = 'inline-flex'; + summarizeBtn.style.alignItems = 'center'; + summarizeBtn.style.justifyContent = 'center'; + summarizeBtn.style.padding = '6px 12px'; + summarizeBtn.style.marginLeft = '12px'; + summarizeBtn.style.backgroundColor = '#ff5757'; + summarizeBtn.style.color = '#ffffff'; + summarizeBtn.style.border = 'none'; + summarizeBtn.style.borderRadius = '4px'; + summarizeBtn.style.cursor = 'pointer'; + summarizeBtn.style.fontWeight = '600'; + summarizeBtn.style.fontSize = '14px'; + summarizeBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)'; + + summarizeBtn.addEventListener('mouseenter', () => { + summarizeBtn.style.backgroundColor = '#e64646'; + }); + summarizeBtn.addEventListener('mouseleave', () => { + summarizeBtn.style.backgroundColor = '#ff5757'; + }); + + actionButtonsContainer.appendChild(summarizeBtn); + + summarizeBtn.addEventListener('click', async () => { + console.log(chrome.i18n.getMessage("summarizeButtonClicked")); + await handleSummarizeButtonClick(); + }); + } + } } // Observe changes in the DOM and try adding the button const observer = new MutationObserver(() => { - addSummarizeButtonIfNeeded(); + addSummarizeButtonIfNeeded(); +}); +observer.observe(document.body, { + childList: true, + subtree: true }); -observer.observe(document.body, { childList: true, subtree: true }); // Run once on load addSummarizeButtonIfNeeded(); + +document.querySelectorAll('[data-i18n]').forEach(element => { + const key = element.getAttribute('data-i18n'); + element.textContent = chrome.i18n.getMessage(key); +}); \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index 57a5eba..648f3d7 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,8 +1,9 @@ { - "name": "YouTube Summarizer MVP", + "name": "__MSG_extensionName__", "version": "0.1", "manifest_version": 3, - "description": "MVP: Summarize YouTube videos using ChatGPT 4o mini API.", + "default_locale": "en", + "description": "__MSG_extensionDescription__", "action":{ "default_popup": "popup.html" }, diff --git a/src/popup.html b/src/popup.html index 8e0e740..466a795 100644 --- a/src/popup.html +++ b/src/popup.html @@ -1,29 +1,25 @@ - -
- - -