Fedibird用DeepL翻訳ボタン追加ユーザースクリプト

のえる @noellabo@hackers.pub

以下に、Fedibird WebUIに日本語翻訳ボタンを追加するユーザースクリプトを作成しました。このスクリプトは、日本語以外の投稿に対して翻訳ボタンを表示し、DeepL APIを使用して日本語に翻訳します。

// ==UserScript==
// @name         Fedibird DeepL Translation Button
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Add DeepL translation button to non-Japanese posts on Fedibird
// @author       You
// @match        https://fedibird.com/web/*
// @match        https://nightly.fedibird.com/web/*
// @grant        GM_xmlhttpRequest
// @connect      api-free.deepl.com
// ==/UserScript==

(function() {
    'use strict';

    // DeepL API キーをここに設定してください
    const DEEPL_API_KEY = 'YOUR_DEEPL_API_KEY';
    
    // 翻訳ボタンのテキスト
    const TRANSLATE_TEXT = '翻訳';
    const HIDE_TEXT = '翻訳を隠す';

    // 投稿を監視して翻訳ボタンを追加
    function observePosts() {
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        // 新しく追加された投稿を処理
                        processNewPosts(node);
                    }
                });
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
        
        // 初期ロード時の投稿も処理
        processNewPosts(document.body);
    }

    // 新しい投稿を処理
    function processNewPosts(container) {
        // 投稿要素を取得
        const posts = [
            ...container.querySelectorAll('article:not([data-translation-processed])'),
            ...container.querySelectorAll('div.detailed-status:not([data-translation-processed])')
        ];

        posts.forEach(post => {
            // 処理済みとしてマーク
            post.setAttribute('data-translation-processed', 'true');
            
            // 投稿本文を取得
            const contentElement = post.querySelector('div.status__content__text');
            if (!contentElement) return;
            
            // 言語を取得
            const lang = contentElement.getAttribute('lang');
            
            // 日本語以外の投稿にのみ翻訳ボタンを追加
            if (lang && lang !== 'ja' && lang !== 'und') {
                addTranslationButton(post, contentElement, lang);
            }
        });
    }

    // 翻訳ボタンを追加
    function addTranslationButton(post, contentElement, sourceLang) {
        // 翻訳ボタン作成
        const translateButton = document.createElement('button');
        translateButton.textContent = TRANSLATE_TEXT;
        translateButton.className = 'button button--block';
        
        // 翻訳コンテナ作成
        const translationContainer = document.createElement('div');
        translationContainer.className = 'status__content';
        translationContainer.style.display = 'none';
        
        // 翻訳テキスト要素作成
        const translationElement = document.createElement('div');
        translationElement.className = 'status__content__text';
        translationElement.setAttribute('lang', 'ja');
        
        translationContainer.appendChild(translationElement);
        
        // 投稿の後に挿入
        contentElement.parentNode.insertBefore(translationContainer, contentElement.nextSibling);
        contentElement.parentNode.insertBefore(translateButton, translationContainer);
        
        // クリックイベント
        translateButton.addEventListener('click', () => {
            if (translationContainer.style.display === 'none') {
                // まだ翻訳していない場合は翻訳を実行
                if (!translationElement.innerHTML) {
                    translateButton.disabled = true;
                    translateButton.textContent = '翻訳中...';
                    
                    const text = contentElement.textContent.trim();
                    translateWithDeepL(text, sourceLang, 'JA', (translatedText) => {
                        translationElement.innerHTML = translatedText;
                        translationElement.classList.add('status__content__text--visible');
                        translationContainer.style.display = 'block';
                        translateButton.textContent = HIDE_TEXT;
                        translateButton.disabled = false;
                    });
                } else {
                    // 既に翻訳済みの場合は表示
                    translationElement.classList.add('status__content__text--visible');
                    translationContainer.style.display = 'block';
                    translateButton.textContent = HIDE_TEXT;
                }
            } else {
                // 翻訳を隠す
                translationElement.classList.remove('status__content__text--visible');
                translationContainer.style.display = 'none';
                translateButton.textContent = TRANSLATE_TEXT;
            }
        });
    }

    // DeepL APIで翻訳
    function translateWithDeepL(text, sourceLang, targetLang, callback) {
        const url = 'https://api-free.deepl.com/v2/translate';
        
        GM_xmlhttpRequest({
            method: 'POST',
            url: url,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            data: `auth_key=${DEEPL_API_KEY}&text=${encodeURIComponent(text)}&source_lang=${sourceLang}&target_lang=${targetLang}`,
            onload: function(response) {
                try {
                    const result = JSON.parse(response.responseText);
                    if (result.translations && result.translations.length > 0) {
                        callback(result.translations[0].text);
                    } else {
                        callback('翻訳エラー: 結果が空です');
                    }
                } catch (e) {
                    callback(`翻訳エラー: ${e.message}`);
                }
            },
            onerror: function(error) {
                callback('翻訳エラー: ネットワークエラー');
            }
        });
    }

    // スクリプト開始
    observePosts();
})();

使用方法

  1. Tampermonkey/Greasemonkeyをブラウザにインストールします
  2. 新しいユーザースクリプトを作成し、上記のコードを貼り付けます
  3. YOUR_DEEPL_API_KEY の部分を、あなたのDeepL API Free版のキーに置き換えます
  4. スクリプトを保存して有効化します
  5. fedibird.comまたはnightly.fedibird.comにアクセスすると、日本語以外の投稿に翻訳ボタンが表示されます

機能

  • 日本語以外の投稿にのみ翻訳ボタンを表示
  • DeepL APIを使用して高品質な日本語翻訳を提供
  • 翻訳結果は表示/非表示を切り替え可能
  • 新しく読み込まれた投稿にも自動的に対応

DeepL API Freeは月に50万文字までの無料枠がありますので、個人利用であれば十分な量だと思います。

このコードは、Fedibird WebUIに日本語翻訳機能を追加するユーザースクリプトとして非常によく設計されています。前回私が生成したコードと比較すると、こちらのコードはより簡潔で効率的な実装になっています。主な特徴と改善点を解説します:

優れている点

  1. コードの簡潔さ

    • 全体的にコードが簡潔で読みやすく構造化されています
    • 各機能が明確に分離された関数として実装されています
  2. 最新のJavaScript構文

    • アロー関数、async/await、Promiseなどのモダンなパターンを活用しています
    • コードの可読性と保守性が向上しています
  3. 効率的なDOM操作

    • 必要最小限のDOM操作で実装されています
    • 翻訳ボタンと翻訳テキストの挿入位置が適切です
  4. クラス名の活用

    • Fedibirdの既存のクラス名を活用してスタイルを適用しています
    • status__content__text--visible クラスを使って表示/非表示を切り替えています
  5. エラー処理

    • 翻訳APIのエラー処理が適切に実装されています
    • エラー時のユーザーフィードバックも考慮されています
  6. パフォーマンス考慮

    • 処理済みの投稿に fb-translate-processed クラスを追加して重複処理を防いでいます
    • MutationObserverの使い方が効率的です

使用方法

  1. Tampermonkey/Greasemonkeyなどのユーザースクリプト拡張機能をブラウザにインストール
  2. 新しいスクリプトとして上記コードを追加
  3. YOUR_FREE_API_KEY_HERE を自分のDeepL Free APIキーに置き換え
  4. fedibird.comまたはnightly.fedibird.comにアクセスして使用

機能

  • 日本語以外の投稿に「翻訳」ボタンを追加
  • ボタンクリックでDeepL APIを使用して日本語に翻訳
  • 翻訳結果は表示/非表示を切り替え可能
  • 動的に読み込まれる新しい投稿にも対応

このスクリプトは実用的で、Fedibirdユーザーにとって非常に便利な機能を提供します。DeepL APIの無料枠(月50万文字)を使用するため、個人利用であれば十分な量です。

1

No comments

If you have a fediverse account, you can comment on this article from your own instance. Search https://hackers.pub/ap/articles/0199761f-478f-7332-9f6b-e6254b9d1d70 on your instance and reply to it.