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();
})();
使用方法
- Tampermonkey/Greasemonkeyをブラウザにインストールします
- 新しいユーザースクリプトを作成し、上記のコードを貼り付けます
YOUR_DEEPL_API_KEYの部分を、あなたのDeepL API Free版のキーに置き換えます- スクリプトを保存して有効化します
- fedibird.comまたはnightly.fedibird.comにアクセスすると、日本語以外の投稿に翻訳ボタンが表示されます
機能
- 日本語以外の投稿にのみ翻訳ボタンを表示
- DeepL APIを使用して高品質な日本語翻訳を提供
- 翻訳結果は表示/非表示を切り替え可能
- 新しく読み込まれた投稿にも自動的に対応
DeepL API Freeは月に50万文字までの無料枠がありますので、個人利用であれば十分な量だと思います。
このコードは、Fedibird WebUIに日本語翻訳機能を追加するユーザースクリプトとして非常によく設計されています。前回私が生成したコードと比較すると、こちらのコードはより簡潔で効率的な実装になっています。主な特徴と改善点を解説します:
優れている点
-
コードの簡潔さ:
- 全体的にコードが簡潔で読みやすく構造化されています
- 各機能が明確に分離された関数として実装されています
-
最新のJavaScript構文:
- アロー関数、async/await、Promiseなどのモダンなパターンを活用しています
- コードの可読性と保守性が向上しています
-
効率的なDOM操作:
- 必要最小限のDOM操作で実装されています
- 翻訳ボタンと翻訳テキストの挿入位置が適切です
-
クラス名の活用:
- Fedibirdの既存のクラス名を活用してスタイルを適用しています
status__content__text--visibleクラスを使って表示/非表示を切り替えています
-
エラー処理:
- 翻訳APIのエラー処理が適切に実装されています
- エラー時のユーザーフィードバックも考慮されています
-
パフォーマンス考慮:
- 処理済みの投稿に
fb-translate-processedクラスを追加して重複処理を防いでいます - MutationObserverの使い方が効率的です
- 処理済みの投稿に
使用方法
- Tampermonkey/Greasemonkeyなどのユーザースクリプト拡張機能をブラウザにインストール
- 新しいスクリプトとして上記コードを追加
YOUR_FREE_API_KEY_HEREを自分のDeepL Free APIキーに置き換え- fedibird.comまたはnightly.fedibird.comにアクセスして使用
機能
- 日本語以外の投稿に「翻訳」ボタンを追加
- ボタンクリックでDeepL APIを使用して日本語に翻訳
- 翻訳結果は表示/非表示を切り替え可能
- 動的に読み込まれる新しい投稿にも対応
このスクリプトは実用的で、Fedibirdユーザーにとって非常に便利な機能を提供します。DeepL APIの無料枠(月50万文字)を使用するため、個人利用であれば十分な量です。