Claude Skills Guide

Translating web page content automatically has become essential for developers working with international users and for power users browsing foreign-language content. Chrome extensions that translate pages offer a flexible solution that runs directly in the browser, processing content without sending entire pages to external services.

This guide walks you through building a chrome extension translate pages feature using the Chrome Extension Manifest V3 architecture. You’ll learn the core patterns, implementation details, and practical considerations for creating a functional translation tool.

Understanding the Translation Extension Architecture

Chrome extension translate pages typically work through a combination of content scripts, background service workers, and popup interfaces. The content script extracts text from the current page, the background script handles API communication with translation services, and the popup provides user controls.

The key advantage of building this as an extension rather than a standalone web app is access to Chrome APIs like chrome.storage for persisting user preferences and chrome.tabs for interacting with active browser sessions.

Here’s a basic Manifest V3 structure for a translation extension:

// manifest.json
{
  "manifest_version": 3,
  "name": "Page Translator",
  "version": "1.0",
  "description": "Translate web pages instantly",
  "permissions": ["activeTab", "storage", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "background": {
    "service_worker": "background.js"
  }
}

Extracting Page Content

The first step in building chrome extension translate pages functionality is capturing the content you want to translate. Content scripts run in the context of web pages and can access the DOM directly.

Here’s a content script that extracts page text while preserving some structure:

// content.js
function extractPageContent() {
  // Get the visible text content
  const body = document.body;
  const textContent = body.innerText || body.textContent;
  
  // Extract with basic paragraph structure
  const paragraphs = Array.from(document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li'))
    .map(el => el.innerText.trim())
    .filter(text => text.length > 0);
  
  return {
    fullText: textContent,
    structured: paragraphs,
    title: document.title,
    url: window.location.href,
    language: document.documentElement.lang || 'unknown'
  };
}

// Send content to background script when requested
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getContent') {
    const content = extractPageContent();
    sendResponse(content);
  }
  return true;
});

This approach captures both the full text for complete translation and structured content for more targeted translation of specific sections.

Handling Translation API Communication

The background service worker manages communication with translation APIs. This separation keeps API keys secure and handles the asynchronous nature of network requests.

// background.js
const TRANSLATION_API = 'https://api.mymemory.translated.net/get';

async function translateText(text, sourceLang, targetLang) {
  const params = new URLSearchParams({
    q: text,
    langpair: `${sourceLang}|${targetLang}`
  });
  
  const response = await fetch(`${TRANSLATION_API}?${params}`);
  
  if (!response.ok) {
    throw new Error(`Translation failed: ${response.status}`);
  }
  
  const data = await response.json();
  return data.responseData.translatedText;
}

async function translateContent(content, targetLang) {
  const results = {
    original: content,
    translated: {}
  };
  
  // Translate each paragraph
  for (const paragraph of content.structured) {
    try {
      results.translated[paragraph] = await translateText(
        paragraph, 
        content.language || 'en', 
        targetLang
      );
    } catch (error) {
      console.error('Translation error:', error);
      results.translated[paragraph] = paragraph; // Fallback to original
    }
  }
  
  return results;
}

// Handle messages from popup or content script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'translate') {
    translateContent(request.content, request.targetLang)
      .then(result => sendResponse(result))
      .catch(error => sendResponse({ error: error.message }));
    return true; // Keep channel open for async response
  }
});

This example uses MyMemory, a free translation API suitable for learning and prototyping. For production use, you’d integrate paid services like Google Translate, DeepL, or Microsoft Translator.

Building the Popup Interface

The popup provides the user interface for selecting target languages and triggering translations:

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 300px; padding: 16px; font-family: system-ui; }
    select, button { width: 100%; padding: 8px; margin: 8px 0; }
    .result { margin-top: 16px; padding: 8px; background: #f5f5f5; max-height: 200px; overflow-y: auto; }
  </style>
</head>
<body>
  <h3>Page Translator</h3>
  <select id="targetLang">
    <option value="es">Spanish</option>
    <option value="fr">French</option>
    <option value="de">German</option>
    <option value="ja">Japanese</option>
    <option value="zh">Chinese</option>
  </select>
  <button id="translateBtn">Translate Page</button>
  <div id="result" class="result"></div>
  <script src="popup.js"></script>
</body>
</html>
// popup.js
document.getElementById('translateBtn').addEventListener('click', async () => {
  const targetLang = document.getElementById('targetLang').value;
  const resultDiv = document.getElementById('result');
  
  resultDiv.textContent = 'Translating...';
  
  // Get the active tab
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  
  // Request content from the page
  chrome.tabs.sendMessage(tab.id, { action: 'getContent' }, async (content) => {
    if (chrome.runtime.lastError) {
      resultDiv.textContent = 'Error: Could not access page content';
      return;
    }
    
    // Send to background for translation
    chrome.runtime.sendMessage(
      { action: 'translate', content, targetLang },
      (response) => {
        if (response.error) {
          resultDiv.textContent = `Error: ${response.error}`;
        } else {
          // Display translated content
          const translated = Object.values(response.translated).join('\n\n');
          resultDiv.textContent = translated;
        }
      }
    );
  });
});

Replacing Page Content with Translations

For a complete page translation experience, you can replace the original content with translated text directly in the page:

// content.js - Add translation application
function applyTranslation(translatedContent) {
  const elements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li');
  let index = 0;
  
  elements.forEach(el => {
    const text = el.innerText.trim();
    if (translatedContent[text] && index < Object.keys(translatedContent).length) {
      el.innerText = translatedContent[text];
      index++;
    }
  });
}

// Listen for translation results
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getContent') {
    const content = extractPageContent();
    sendResponse(content);
  } else if (request.action === 'applyTranslation') {
    applyTranslation(request.translated);
    sendResponse({ success: true });
  }
  return true;
});

Handling Language Detection

Detecting the source language automatically improves user experience. You can implement detection by analyzing the page’s HTML lang attribute or using language detection libraries:

// content.js - Improved language detection
function detectLanguage() {
  // Check HTML lang attribute first
  const htmlLang = document.documentElement.lang;
  if (htmlLang && htmlLang.length === 2) {
    return htmlLang;
  }
  
  // Check meta tags
  const metaTags = document.querySelectorAll('meta[content*="lang"]');
  for (const meta of metaTags) {
    const match = meta.content.match(/lang=["']?([a-z]{2})/);
    if (match) return match[1];
  }
  
  // Default to English if detection fails
  return 'en';
}

Performance and User Experience Considerations

When building production-ready chrome extension translate pages, consider these practical aspects:

Translation Caching: Store previously translated content to avoid redundant API calls:

// background.js - Simple caching
const translationCache = new Map();

function getCachedTranslation(text, sourceLang, targetLang) {
  const key = `${text.slice(0, 50)}|${sourceLang}|${targetLang}`;
  return translationCache.get(key);
}

function setCachedTranslation(text, sourceLang, targetLang, translated) {
  const key = `${text.slice(0, 50)}|${sourceLang}|${targetLang}`;
  translationCache.set(key, translated);
}

Partial Translation: Not all content needs translation. Skip navigation elements, code blocks, and certain UI components to maintain page functionality:

function shouldTranslate(element) {
  const skipClasses = ['code', 'syntax', 'nav', 'menu', 'footer'];
  return !element.className.split(' ').some(cls => skipClasses.includes(cls));
}

User Preferences: Persist language choices and API keys using chrome.storage for a better experience across sessions.

Conclusion

Building chrome extension translate pages requires understanding content script injection, background service worker communication, and translation API integration. The patterns covered here provide a foundation that you can extend with additional features like text-to-speech for translated content, offline translation using WebAssembly models, or integration with machine learning APIs for better accuracy.

The key is starting simple—extracting text, translating through an API, and displaying results—then gradually adding features like content replacement, caching, and language detection based on user needs.

For developers looking to customize their translation experience, the Chrome Extension APIs offer flexibility in how you capture, process, and display translated content. Power users benefit from having translation functionality integrated directly into their browser workflow without needing separate tools or copy-pasting content between applications.

Built by theluckystrike — More at zovo.one