This gist explains how to integrate a custom OpenAI translator backend into the Linguist Chrome extension.
Source: https://github.com/translate-tools/linguist
Issue: translate-tools/linguist#230 (comment)
Go to Setting -> Custom translators -> Add
class OpenAITranslator {
#apiKey = 'API_KEY'; // Replace with your actual OpenAI API key
#endpoint = 'https://api.openai.com/v1/chat/completions';
#model = 'gpt-4.1-nano'; // Fallback to 'gpt-3.5-turbo' if needed
#defaultPrompt = 'You are a professional foreign language translator. Translate the text from ${from} to ${to}, preserving the original meaning, style, and tone. Adapt idioms or cultural references to be understandable for native speakers of the target language. Translate accurately and naturally.';
async translate(text, from, to) {
if (!text) throw new Error('No text provided for translation');
const result = await this.#fetchTranslation(text, from, to);
if (!result) throw new Error(`Translation failed for text: "${text}" from ${from} to ${to}`);
return result;
}
async translateBatch(texts, from, to) {
if (!texts?.length) throw new Error('No texts provided for batch translation');
const results = await Promise.allSettled(texts.map(text => this.translate(text, from, to)));
return results.map((result, index) => {
if (result.status === 'fulfilled') return result.value;
console.error(`Batch translation failed for text[${index}]:`, result.reason);
return texts[index]; // Fallback to original text on failure
});
}
getLengthLimit() {
return 4000; // Conservative limit for OpenAI models
}
getRequestsTimeout() {
return 1000; // 1 second to respect OpenAI rate limits
}
checkLimitExceeding(text) {
const limit = this.getLengthLimit();
if (Array.isArray(text)) {
const totalLength = text.reduce((sum, t) => sum + (t?.length || 0), 0);
return totalLength > limit ? totalLength - limit : 0;
}
return text?.length > limit ? text.length - limit : 0;
}
static isSupportedAutoFrom() {
return true; // OpenAI can detect source language
}
static getSupportedLanguages() {
return [
'en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'zh', 'ja', 'ko',
'ar', 'nl', 'sv', 'tr', 'pl', 'hi', 'vi', 'th', 'el', 'he'
];
}
async #fetchTranslation(text, from, to) {
try {
const response = await fetch(this.#endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.#apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: this.#model,
messages: [
{ role: 'system', content: this.#defaultPrompt.replace('${from}', from).replace('${to}', to) },
{ role: 'user', content: `Translate: ${text}` }
],
max_tokens: 1000,
temperature: 0.3
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`HTTP ${response.status}: ${errorData.error?.message || response.statusText}`);
}
const data = await response.json();
if (!data.choices?.[0]?.message?.content) {
throw new Error('Invalid response from OpenAI: No translation content');
}
return data.choices[0].message.content.trim();
} catch (error) {
console.error('Translation error:', error);
throw error;
}
}
}
OpenAITranslator;