Translate Selection Sidebar and Tooltip

Translate selected text, show in a tooltip, add to a sidebar list, and store in local storage

目前为 2024-08-10 提交的版本。查看 最新版本

// ==UserScript==
// @name         Translate Selection Sidebar and Tooltip
// @namespace    https://aveusaid.wordpress.com
// @version      0.9082024
// @description  Translate selected text, show in a tooltip, add to a sidebar list, and store in local storage
// @author       Usaid Bin Khalid Khan
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Add CSS for the tooltip, sidebar, buttons, and dark mode text
    const styleElement = document.createElement('style');
    styleElement.type = 'text/css';
    styleElement.innerHTML = `
        .translator-tooltip {
            font-weight: 700;
            color: #000000;
            position: absolute;
            z-index: 10000;
            padding: 8px 12px;
            max-width: 300px;
            border-radius: 0.3em;
            background-color: #ffffdb;
            border: 1px solid #ccc;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            text-align: center;
            font-size: 18px;
            line-height: 1.4;
            visibility: hidden;
            opacity: 0;
            transition: visibility 0s linear 300ms, opacity 300ms;
        }

        .translator-tooltip.visible {
            visibility: visible;
            opacity: 1;
        }

        .translator-sidebar {
            position: fixed;
            top: 0;
            right: 0;
            width: 280px;
            height: 100%;
            background-color: #f7f7f7;
            overflow-y: auto;
            border-left: 2px solid #3388CC;
            padding: 20px;
            z-index: 10000;
            font-size: 16px;
            overflow: auto;
            box-shadow: -2px 0 5px rgba(0,0,0,0.1);
            box-sizing: border-box;
            resize: horizontal;
            min-width: 200px;
            max-width: 500px;
            display: none;
        }

        .translator-sidebar > * {
            margin-bottom: 20px;
        }

        .translator-entry {
            display: flex;
            flex-direction: column;
            padding: 10px 0;
            margin-bottom: 10px;
            border-bottom: 1px solid #ccc;
        }

        .translator-entry span:first-child {
            margin-bottom: 6px;
            font-weight: bold;
            color: #333;
        }

        .translator-entry span:last-child {
            color: #666;
        }

        .close-sidebar {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            font-weight: bold;
            color: #555;
            font-size: 20px;
            background-color: transparent;
            border: none;
            z-index: 10001;
        }

        .attractive-text {
            display: block;
            font-size: 14px;
            font-style: italic;
            text-decoration: none;
            color: #ff6666;
            margin-top: 20px;
            text-align: center;
        }

        .attractive-text:hover {
            color: #ff3333;
        }

        .clear-button,
        .copy-all-button,
        .mode-toggle {
            cursor: pointer;
            padding: 10px 15px;
            background-color: #f5f5f5;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            font-size: 16px;
            margin: 10px 0;
            width: calc(100% - 20px);
            box-sizing: border-box;
        }

        .clear-button {
            margin-top: 20px;
        }

        .mode-toggle {
            display: flex;
            align-items: center;
            margin-top: 20px;
        }

        .mode-toggle span {
            margin-right: 10px;
        }

        .close-sidebar {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            font-weight: bold;
            color: #555;
            font-size: 20px;
            background-color: transparent;
            border: none;
            z-index: 10001;
        }

        .attractive-text {
            display: block;
            font-size: 14px;
            font-style: italic;
            text-decoration: none;
            color: #ff6666;
            margin-top: 20px;
            text-align: center;
        }

        .attractive-text:hover {
            color: #ff3333;
        }

        .error-message {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            padding: 20px;
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
            border-radius: 5px;
            z-index: 10002;
            font-size: 18px;
            text-align: center;
            visibility: hidden;
            opacity: 0;
            transition: visibility 0s linear 300ms, opacity 300ms;
        }

        .error-message.visible {
            visibility: visible;
            opacity: 1;
        }

        .open-sidebar-button {
            position: fixed;
            bottom: 140px;
            right: 20px;
            z-index: 10001;
            cursor: pointer;
            padding: 10px 15px;
            background-color: #f5f5f5;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            font-size: 16px;
            display: block;
        }

        .empty-sidebar {
            font-style: italic;
            color: grey;
            margin-top: 20px;
            text-align: center;
        }

        .dark-mode .translator-sidebar {
            background-color: #333333; /* Dark background color */
            color: #ffffff; /* White text */
        }

        .dark-mode .translator-entry span:first-child {
            color: #FFD700; /* Sunset yellow */
        }

        .dark-mode .translator-entry span:last-child {
            color: #ffffff; /* White */
        }

        .dark-mode .clear-button,
        .dark-mode .open-sidebar-button,
        .dark-mode .copy-all-button {
            background-color: #444;
            color: #FFD700; /* Sunset yellow */
            border-color: #666;
        }
    `;
    document.head.appendChild(styleElement);

    // Create tooltip element
    const tooltip = document.createElement('div');
    tooltip.className = 'translator-tooltip';
    document.body.appendChild(tooltip);

    // Create sidebar element
    const sidebar = document.createElement('div');
    sidebar.className = 'translator-sidebar resizable';
    document.body.appendChild(sidebar);

    // Create close button for sidebar
    const closeButton = document.createElement('button');
    closeButton.innerHTML = '×';
    closeButton.className = 'close-sidebar';
    closeButton.setAttribute('aria-label', 'Close sidebar');
    sidebar.appendChild(closeButton);

    // Create "Cogito, Ergo Sum" text
    const attractText = document.createElement('a');
    attractText.innerHTML = 'Cogito, Ergo Sum';
    attractText.href = 'https://aveusaid.wordpress.com';
    attractText.className = 'attractive-text';
    attractText.setAttribute('role', 'link');
    attractText.setAttribute('aria-label', 'Cogito, Ergo Sum');
    sidebar.appendChild(attractText);

    // Add two italic lines for empty sidebar
    const emptyLines = document.createElement('div');
    emptyLines.className = 'empty-sidebar';
    emptyLines.innerHTML = `
        <i>The Archives are empty.</i> <br>
        <i>Select some text to add it to the Translation Archives.</i>
    `;
    sidebar.appendChild(emptyLines);

    // Create clear button
    const clearButton = document.createElement('button');
    clearButton.textContent = 'Tabula Rasa';
    clearButton.className = 'clear-button';
    clearButton.setAttribute('aria-label', 'Clear translations');
    sidebar.appendChild(clearButton);

    // Create copy all button
    const copyAllButton = document.createElement('button');
    copyAllButton.textContent = 'Copy All';
    copyAllButton.className = 'copy-all-button';
    copyAllButton.setAttribute('aria-label', 'Copy all translations');
    sidebar.appendChild(copyAllButton);

    // Create mode toggle button
    const modeToggleButton = document.createElement('button');
    modeToggleButton.className = 'mode-toggle';
    modeToggleButton.textContent = 'Dim the Lights';
    sidebar.appendChild(modeToggleButton);

    // Create open sidebar button
    const openSidebarButton = document.createElement('button');
    openSidebarButton.textContent = 'The Archive';
    openSidebarButton.className = 'open-sidebar-button';
    openSidebarButton.setAttribute('aria-label', 'Open translation archive');
    document.body.appendChild(openSidebarButton);

    // Error message element
    const errorMessage = document.createElement('div');
    errorMessage.className = 'error-message';
    document.body.appendChild(errorMessage);

    let translations = [];

    function showError(message) {
        errorMessage.textContent = message;
        errorMessage.classList.add('visible');
        setTimeout(() => {
            errorMessage.classList.remove('visible');
        }, 3000);
    }

    function translateText(text) {
        return fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=${encodeURIComponent(text)}`)
            .then(response => response.json())
            .then(result => result[0][0][0])
            .catch(error => {
                showError('ERROR: The Network Towers Have Fallen!');
                console.error(error);
            });
    }

    function updateSidebar() {
        sidebar.innerHTML = '';
        sidebar.appendChild(closeButton);
        sidebar.appendChild(attractText);
        if (translations.length === 0) {
            sidebar.appendChild(emptyLines);
        } else {
            translations.forEach(({ original, translated }) => {
                const entry = document.createElement('div');
                entry.className = 'translator-entry';
                entry.innerHTML = `
                    <span>${original}</span>
                    <span>${translated}</span>
                `;
                sidebar.appendChild(entry);
            });
        }
        sidebar.appendChild(clearButton);
        sidebar.appendChild(copyAllButton);
        sidebar.appendChild(modeToggleButton);
    }

    function toggleSidebar() {
        if (sidebar.style.display === 'block') {
            sidebar.style.display = 'none';
            openSidebarButton.style.display = 'block';  // Show "The Archive" button
        } else {
            updateSidebar();
            sidebar.style.display = 'block';
            openSidebarButton.style.display = 'none';  // Hide "The Archive" button
        }
    }

    function clearTranslations() {
        translations = [];
        updateSidebar();
    }

    function copyAllTranslations() {
        const allTranslations = translations.map(({ original, translated }) => `${original}: ${translated}`).join('\n');
        navigator.clipboard.writeText(allTranslations).then(() => {
            showError('Translations copied to clipboard!');
        }).catch(() => {
            showError('Failed to copy translations.');
        });
    }

    function toggleDarkMode() {
        document.body.classList.toggle('dark-mode');
        const isDarkMode = document.body.classList.contains('dark-mode');
        modeToggleButton.textContent = isDarkMode ? 'Light the Way' : 'Dim the Lights';
    }

    document.addEventListener('mouseup', async (e) => {
        if (window.getSelection().toString()) {
            const selectedText = window.getSelection().toString();
            const translatedText = await translateText(selectedText);
            translations.push({ original: selectedText, translated: translatedText });
            updateSidebar();
            tooltip.textContent = translatedText;
            tooltip.style.left = `${e.pageX}px`;
            tooltip.style.top = `${e.pageY + 10}px`;
            tooltip.classList.add('visible');
            setTimeout(() => {
                tooltip.classList.remove('visible');
            }, 3000);
        }
    });

    openSidebarButton.addEventListener('click', toggleSidebar);
    closeButton.addEventListener('click', toggleSidebar);
    clearButton.addEventListener('click', clearTranslations);
    copyAllButton.addEventListener('click', copyAllTranslations);
    modeToggleButton.addEventListener('click', toggleDarkMode);

})();