Skip to content

Instantly share code, notes, and snippets.

@flipeador
Last active March 7, 2026 00:39
Show Gist options
  • Select an option

  • Save flipeador/56ca9b5798987d175f0a5e9bc97f6977 to your computer and use it in GitHub Desktop.

Select an option

Save flipeador/56ca9b5798987d175f0a5e9bc97f6977 to your computer and use it in GitHub Desktop.
Translate Bluesky posts using the Translator API.
// ==UserScript==
// @name Bluesky Translate
// @author Flipeador
// @version 1.0.3
// @icon https://www.google.com/s2/favicons?sz=128&domain=bsky.app
// @homepageURL https://gist.github.com/flipeador/56ca9b5798987d175f0a5e9bc97f6977
// @downloadURL https://gist.github.com/flipeador/56ca9b5798987d175f0a5e9bc97f6977/raw/bluesky-translate.js
// @match https://bsky.app/*
// @run-at document-idle
// ==/UserScript==
// https://developer.chrome.com/docs/ai/built-in
const gtUrl = 'https://translate.google.com/';
const selector = `a[href^="${gtUrl}"]:not([_])`;
async function main() {
for (const $a of document.querySelectorAll(selector)) {
$a.setAttribute('_', '');
$a.setAttribute('title', 'Detecting language…');
const text = new URL($a.href).searchParams.get('text');
const detectedLanguage = await detectLanguage(text);
if (!detectedLanguage || Error.isError(detectedLanguage)) {
$a.title = detectedLanguage?.toString?.() ?? 'Unknown language';
continue;
}
const [hrLangTagFrom, hrLangTagTo] = displayNames([detectedLanguage, navigator.language]);
$a.setAttribute('title', `${hrLangTagFrom} → ${hrLangTagTo}`);
async function onTranslate(event) {
event.preventDefault();
event.stopImmediatePropagation();
const $p = await translate(text, detectedLanguage);
$a.parentElement.insertAdjacentElement('afterend', $p);
}
$a.addEventListener('click', onTranslate, { once: true });
}
setTimeout(main, 2500);
}
// https://developer.mozilla.org/docs/Web/API/Translator
async function translate(text, from, to) {
const element = document.createElement('p');
function onProgress(event) {
const progress = (event.loaded * 100).toFixed(2);
element.textContent = `Downloaded: ${progress}%`;
}
try {
const availability = await findAvailableLanguage(from, to);
availability.monitor = m => m.addEventListener('downloadprogress', onProgress);
const translator = await Translator.create(availability);
const stream = translator.translateStreaming(text);
element.textContent = '';
for await (const chunk of stream)
element.textContent += chunk;
} catch (error) {
element.textContent = `ERROR: ${error.message}`;
}
return element;
}
async function findAvailableLanguage(sourceLanguage, targetLanguages) {
targetLanguages ??= navigator.languages;
for (const targetLanguage of targetLanguages) {
const options = { sourceLanguage, targetLanguage };
options.availability = await Translator.availability(options);
if (options.availability !== 'unavailable') return options;
}
}
// https://developer.mozilla.org/docs/Web/API/LanguageDetector
async function detectLanguage(text) {
detectLanguage.detector ??= LanguageDetector.create();
const detector = await detectLanguage.detector.catch(e => e);
if (Error.isError(detector)) return detector;
const { detectedLanguage } = (await detector.detect(text))[0];
return detectedLanguage === 'und' ? null : detectedLanguage;
}
function displayNames(codes, options={ type: 'language' }) {
const displayNames = new Intl.DisplayNames([navigator.language], options);
return codes.map(code => displayNames.of(code));
}
main();
@flipeador
Copy link
Author

flipeador commented May 21, 2025

Bluesky Translate

Translate Bluesky posts using the Translator API, available from Chrome 138 stable.

Note

  • Hover over the Translate link to see the current state.
  • The Translate link restores its default behavior after the first click.
  • You may have to wait a few seconds for LanguageDetector to download.

Installation

  1. Install the Tampermonkey browser extension.
  2. Import the script from the extension Dashboard.

@flipeador
Copy link
Author

flipeador commented Jan 30, 2026

You might have to enable Experimental translation API in Experimental features.


Note

This script no longer works, and I don't feel like fixing it.
See bluesky-social/social-app#9070.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment