Skip to content

Instantly share code, notes, and snippets.

@flipeador
Last active July 22, 2025 01:54
Show Gist options
  • Save flipeador/56ca9b5798987d175f0a5e9bc97f6977 to your computer and use it in GitHub Desktop.
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.1
// @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([_])`;
const detector = LanguageDetector.create();
const [hrLangTagTo] = displayNames([navigator.language]);
async function main() {
for (const $a of document.querySelectorAll(selector)) {
$a.setAttribute('_', '');
let text = new URL($a.href).searchParams.get('text');
const [result] = await (await detector).detect(text);
if (result.detectedLanguage !== 'und' && result.confidence >= 0.1) {
const [hrLangTagFrom] = displayNames([result.detectedLanguage]);
$a.setAttribute('title', `${hrLangTagFrom} → ${hrLangTagTo}`);
async function onTranslate(event) {
event.preventDefault();
event.stopImmediatePropagation();
const $p = await translate(text, result.detectedLanguage);
$a.parentElement.insertAdjacentElement('afterend', $p);
}
$a.addEventListener('click', onTranslate, { once: true });
}
}
setTimeout(main, 2500);
}
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}%`;
}
const availability = await findAvailableLanguage(from, to);
const sourceLanguage = from, { targetLanguage } = availability;
const monitor = m => m.addEventListener('downloadprogress', onProgress);
const p = Translator.create({ monitor, sourceLanguage, targetLanguage });
const promise = p.then(translator => translator.translateStreaming(text));
promise.then(async stream => {
element.textContent = '';
for await (const chunk of stream)
element.textContent += chunk;
});
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;
}
}
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

The Translate link restores its default behavior after the first click.

Installation

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

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