BlueskyTranslateButton

Blueskyの本文の下に翻訳ボタンを追加し、クリックすると翻訳文を表示する(別ページを開かない)

< Feedback on BlueskyTranslateButton

Review: Good - script works

§
Posted: 27-05-2023

GitHubレポジトリとかは無いようなので直接書きますが、公式クライアントだけでなくUcho-ten、Laikaにも対応したバージョンです。

// ==UserScript==
// @name         BlueskyTranslateButton
// @namespace    https://nigauri.me/
// @version      0.2
// @description  Blueskyの本文の下に翻訳ボタンを追加し、クリックすると翻訳文を表示する
// @author       nigauri
// @match        https://bsky.app/*
// @match        https://staging.bsky.app/*
// @match        https://ucho-ten.net*
// @match        https://laika-bluesky.web.app/*
// @icon         https://bsky.app/static/apple-touch-icon.png
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const APP = {
        BSKY: 'value1',
        UCHO_TEN: 'value2',
        LAIKA: 'value3'
    };

    // setting
    const translateButtonLabel = "Translate...";
    const sourceLang = "en";
    const targetLang = "ja";

    // css
    const style = `<style>
        .translateBlock .translateBtn {
            color: rgb(0, 133, 255);
            text-decoration: none;
            cursor: pointer;
        }
        .translateBlock .translateText {
            display: none;
        }
        .translateBlock .translateText p {
            margin: 0.5rem 0;
        }
    </style>`;
    $("head").append(style);

    // ----------
    const host = window.location.host;
    let app;
    if(host.endsWith("bsky.app")) app = APP.BSKY;
    else if(host.endsWith("ucho-ten.net")) app = APP.UCHO_TEN;
    else if(host.endsWith("laika-bluesky.web.app")) app = APP.LAIKA;
    else return;

    let observeTarget;
    switch(app){
      case APP.BSKY:
        observeTarget = "#root";
        break;
      case APP.UCHO_TEN:
        observeTarget = ".nextui-c-lhoAZR > div";
        break;
      case APP.LAIKA:
        observeTarget = "body > app-root > main > app-home > main > section > app-timeline";
        break;
    }
    const observeOption = {
        childList: true,
        subtree: true,
    }

    let postTextCSS;
    switch(app){
      case APP.BSKY:
        postTextCSS = ".css-175oi2r > .css-175oi2r.r-1awozwy.r-18u37iz.r-1w6e6rj > .css-1rynq56";
        break;
      case APP.UCHO_TEN:
        postTextCSS = 'div[style="word-break: break-all;"]';
        break;
      case APP.LAIKA:
        postTextCSS = ".feed_content, .feed_quote_content, .reply_bottom";
        break;
    }
    const marker = "ngurtb";

    let translateBtnObserver = new MutationObserver(function (MutationRecords, MutationObserver) {
        translateBtnObserver.disconnect();
        $(postTextCSS).not(`.${marker}`).each(function(i, elem) {
            $(elem).addClass(marker);
            let parent = app==APP.BSKY ? $(elem).parent() : $(elem);
            parent.after(`<div class="translateBlock"><div class="translateBtn">${translateButtonLabel}</div><div class="translateText"></div></div>`);
            let translateBtn = parent.parent().find(".translateBtn");
            let translateText = parent.parent().find(".translateText");
            setTranslateBlock(translateBtn, translateText, $(elem));
        });
        translateBtnObserver.observe($(observeTarget).get(0), observeOption);
    });
    function addObserverIfDesiredNodeAvailable() {
        const composeBox = $(observeTarget).get(0);
        if(!composeBox) {
            //The node we need does not exist yet.
            //Wait 500ms and try again
            window.setTimeout(addObserverIfDesiredNodeAvailable,500);
            return;
        }
        translateBtnObserver.observe(composeBox, observeOption);

    }
    addObserverIfDesiredNodeAvailable();

    function setTranslateBlock(translateBtn, translateText, elem) {
        translateText.css("color", elem.css("color"));
        translateBtn.on("click", function(){
            translateBtnObserver.disconnect();
            let encodeText = encodeURIComponent(elem.text());
            let url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&q=${encodeText}`;
            $.ajaxSetup({async: false});
            $.getJSON(url, function(data) {
                let text = "";
                data[0].forEach(function(element){
                    text += `<p>${escapeHtml(element[0])}</p>`;
                });
                translateText.html(text);
                translateText.show();
                $(this).off("click");
            });
            $.ajaxSetup({async: true});
            translateBtnObserver.observe($(observeTarget).get(0), observeOption);
            return false;
        });
    }

    function escapeHtml(str) {
        var patterns = {
            '<'  : '&lt;',
            '>'  : '&gt;',
            '&'  : '&amp;',
            '"'  : '&quot;',
            '\'' : '&#x27;',
            '`'  : '&#x60;'
        };
        return str.replace(/[<>&"'`]/g, function(match) {
            return patterns[match];
        });
    };
})();

Post reply

Sign in to post a reply.