Skip to content

Instantly share code, notes, and snippets.

@atomlab
Last active July 29, 2025 16:31
Show Gist options
  • Save atomlab/19ce55f47414fa9c57a9aae8816b1ea8 to your computer and use it in GitHub Desktop.
Save atomlab/19ce55f47414fa9c57a9aae8816b1ea8 to your computer and use it in GitHub Desktop.
linguist Translator OpenAI support

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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment