Claude Skills Guide

AI Translation Chrome Extension: A Developer Guide

Building an AI-powered translation extension for Chrome combines browser extension development with modern machine learning APIs. This guide covers the essential components, architectural decisions, and practical code patterns you need to create a functional translation tool.

Core Architecture

A translation Chrome extension operates through several interconnected components. The content script captures selected text or page content. A background service worker handles API communication and state management. The popup interface provides user controls for language selection and translation display.

The AI component typically runs through external APIs—services like OpenAI, Anthropic, Google Cloud Translation, or self-hosted models. The extension acts as a bridge between the user’s browser context and these inference endpoints.

Manifest Configuration

Your extension begins with the manifest file. Version 3 is required for modern extensions:

{
  "manifest_version": 3,
  "name": "AI Translator",
  "version": "1.0",
  "permissions": [
    "activeTab",
    "scripting",
    "storage"
  ],
  "host_permissions": [
    "https://api.openai.com/*",
    "https://api.anthropic.com/*"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}

The host_permissions field is critical—without it, your extension cannot make requests to external AI APIs.

Capturing Text for Translation

Content scripts enable interaction with page content. You have several approaches for capturing text:

Selection-based translation triggers when users highlight text:

// content.js
document.addEventListener('mouseup', async (event) => {
  const selection = window.getSelection().toString().trim();
  if (selection.length > 0 && selection.length < 5000) {
    chrome.runtime.sendMessage({
      type: 'TRANSLATE_SELECTION',
      text: selection,
      tabId: chrome.runtime.id
    });
  }
});

Page-level translation processes entire documents. This requires careful handling to avoid translating UI elements like navigation and buttons:

// content.js - translation scanner
function scanPageForTranslateableContent() {
  const textNodes = [];
  const walker = document.createTreeWalker(
    document.body,
    NodeFilter.SHOW_TEXT,
    null,
    false
  );
  
  let node;
  while (node = walker.nextNode()) {
    const text = node.textContent.trim();
    if (text.length > 20 && !isUIElement(node.parentElement)) {
      textNodes.push({
        text: text,
        element: node.parentElement
      });
    }
  }
  return textNodes;
}

Building the Translation Service

The background service worker handles API communication. This separation keeps your API keys secure and manages rate limiting:

// background.js
const TRANSLATION_API = 'https://api.openai.com/v1/chat/completions';

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'TRANSLATE_SELECTION') {
    handleTranslation(message.text, message.targetLang)
      .then(result => sendResponse({ success: true, translation: result }))
      .catch(error => sendResponse({ success: false, error: error.message }));
    return true; // Keep message channel open for async response
  }
});

async function handleTranslation(text, targetLang) {
  const response = await fetch(TRANSLATION_API, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${await getApiKey()}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        {
          role: 'system',
          content: `Translate the following text to ${targetLang}. Provide only the translation, no explanations.`
        },
        {
          role: 'user',
          content: text
        }
      ]
    })
  });
  
  const data = await response.json();
  return data.choices[0].message.content;
}

Managing API Keys Securely

Never embed API keys directly in your extension code. Use Chrome’s storage API with proper access controls:

// background.js - API key management
async function getApiKey() {
  const result = await chrome.storage.local.get(['apiKey']);
  if (!result.apiKey) {
    throw new Error('API key not configured. Please set your API key in extension settings.');
  }
  return result.apiKey;
}

// Options page saves the key
document.getElementById('saveKey').addEventListener('click', async () => {
  const apiKey = document.getElementById('apiKeyInput').value;
  await chrome.storage.local.set({ apiKey });
  alert('API key saved securely.');
});

Displaying Translations

Several approaches exist for showing translations to users. The choice depends on your use case:

Popup display works for quick translations of selected text:

// background.js - sending translation to popup
chrome.runtime.sendMessage({
  type: 'SHOW_TRANSLATION',
  original: originalText,
  translation: translatedText,
  targetLang: targetLang
});

Inline replacement replaces selected text with translated content:

// content.js
function showInlineTranslation(originalText, translation, range) {
  const span = document.createElement('span');
  span.className = 'ai-translation-highlight';
  span.style.backgroundColor = '#e8f5e9';
  span.style.padding = '2px 4px';
  span.style.borderRadius = '3px';
  span.textContent = translation;
  
  range.deleteContents();
  range.insertNode(span);
  
  // Auto-remove after 10 seconds
  setTimeout(() => span.remove(), 10000);
}

Language Detection

Modern AI APIs often handle language detection automatically. If you need manual detection:

async function detectLanguage(text) {
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${await getApiKey()}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: 'Respond with only the language name.' },
        { role: 'user', content: `What language is this: "${text.substring(0, 100)}"` }
      ]
    })
  });
  
  return (await response.json()).choices[0].message.content.toLowerCase();
}

Handling Rate Limits and Errors

Production extensions must handle API failures gracefully:

async function translateWithRetry(text, targetLang, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await handleTranslation(text, targetLang);
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries - 1) {
        await new Promise(r => setTimeout(r, 2000 * (attempt + 1)));
        continue;
      }
      throw error;
    }
  }
}

Extension Publishing Considerations

When preparing for the Chrome Web Store, ensure your extension meets specific requirements. Privacy policies are mandatory for extensions requesting broad permissions. The review process typically takes 1-3 days but can extend during peak periods.

Consider implementing a free tier with limited translations to demonstrate value before requiring payment for API key setup. This approach reduces friction for users evaluating your extension.

Summary

Building an AI translation Chrome extension requires careful attention to security, user experience, and error handling. The architecture separates concerns between content scripts, background workers, and popup interfaces. API key management uses Chrome’s storage API rather than embedded secrets. Multiple display options—popup, inline, or page-level—serve different use cases.

The foundation established here scales from simple selection translation to complex document processing. As AI models improve, your extension can incorporate new capabilities without fundamental architectural changes.

Built by theluckystrike — More at zovo.one

Step-by-Step: Building the Translation Pipeline

  1. Set up Manifest V3 with contextMenus, storage, and activeTab permissions.
  2. Add a context menu entry: when the user right-clicks on selected text, show “Translate with AI”. Register it in the background service worker on chrome.runtime.onInstalled.
  3. Detect source language automatically: pass the selected text to your translation API without specifying a source language — most models detect it reliably for common languages.
  4. Show a floating tooltip: inject a small overlay near the selection that displays the translated text. Position it using getBoundingClientRect() on the selection’s range object.
  5. Cache translations: store { sourceText, sourceLang, targetLang, translation } in chrome.storage.session keyed by a hash of the source text and language pair. Subsequent translations of the same phrase are instant without hitting the API again.
  6. Add a settings popup: let users select their preferred target language, toggle auto-translate on page load, and enter their API key (stored encrypted in chrome.storage.sync).

Handling the Chrome AI APIs (Built-in Translation)

Chrome 138+ ships the translation API as an origin trial, which enables on-device translation without any external API call:

// Check if the built-in Translation API is available
if ('translation' in self && 'createTranslator' in self.translation) {
  const translator = await self.translation.createTranslator({
    sourceLanguage: 'es',
    targetLanguage: 'en',
  });
  const result = await translator.translate('Hola, mundo');
  console.log(result); // "Hello, world"
}

For users on older Chrome versions, fall back to a cloud API. The extension can detect which path to use at runtime and only prompt for an API key when the built-in API is unavailable.

Comparison with Existing Translation Extensions

Tool Translation engine Offline support API key required Cost
This extension Configurable (Claude/GPT/built-in) With Chrome AI API Optional Free (build it)
Google Translate extension Google NMT No No Free
DeepL extension DeepL No DeepL Pro account Free/Pro
Immersive Translate Multiple No Optional Free/Pro
Lingvanex Lingvanex Yes (premium) Yes Freemium

The key differentiator of a custom-built extension is the ability to use a model that fits your specific domain. Legal, medical, and technical translations often benefit from a domain-aware model rather than a general-purpose one.

Advanced: Page-Level Auto-Translation

For users reading foreign-language documentation regularly, add an auto-translate mode that replaces all text nodes on the page with their translations:

async function translatePage(targetLang) {
  const walker = document.createTreeWalker(
    document.body,
    NodeFilter.SHOW_TEXT,
    { acceptNode: (node) => node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }
  );

  const chunks = [];
  let node;
  while ((node = walker.nextNode())) {
    chunks.push({ node, text: node.textContent });
  }

  // Batch translate to reduce API calls
  const texts = chunks.map(c => c.text);
  const translations = await batchTranslate(texts, targetLang);
  chunks.forEach((chunk, i) => {
    chunk.node.textContent = translations[i];
  });
}

Batch translation reduces API calls from hundreds to a handful per page by grouping text nodes and sending them together.

Troubleshooting

Tooltip appearing behind page elements: Set z-index: 2147483647 on the tooltip container (the maximum CSS z-index value) and use position: fixed instead of position: absolute so it is not clipped by overflow: hidden ancestors.

API key exposed in extension source: Never hard-code the key. Store it in chrome.storage.sync after the user enters it in the options page. The key is then only in the user’s browser storage, not in the extension package that is distributed through the Web Store.

Translation breaking on React/Vue pages: Single-page apps re-render the DOM after navigation, undoing direct text node replacements. Use a MutationObserver to re-apply translations to newly added nodes, and skip nodes that already contain translated text by checking a custom data attribute like data-translated="true".